diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json
index 99764aa6..fb20a9ac 100644
--- a/.changelog/lang_zh-Hans.json
+++ b/.changelog/lang_zh-Hans.json
@@ -4,7 +4,6 @@
"released_date": "2025/01/03",
"fix": [
"plugins.load 方法无法正常加载插件的问题 _[`issue #290`](http://issues.autojs6.com/290)_",
- "dex 文件可能因权限问题无法正常加载的问题 (试修) _[`issue #290`](http://issues.autojs6.com/290)_",
"dx 库在 Android 7.x 无法正常使用的问题 _[`issue #293`](http://issues.autojs6.com/293)_",
"ScriptRuntime 使用 require 引用内置模块时可能出现的同步状态异常 (试修) _[`issue #298`](http://issues.autojs6.com/298)_",
"notice 模块缺失 getBuilder 等扩展方法的问题 _[`issue #301`](http://issues.autojs6.com/301)_",
@@ -15,6 +14,7 @@
"打包页面支持 Pinyin 库选项",
"APK 文件类型信息对话框增加文件大小与签名方案信息",
"APK 文件类型信息对话框增加点击监听器支持文本复制与应用详情跳转",
+ "尝试恢复 com.stardust 前缀包以便提升代码兼容性 _[`issue #290`](http://issues.autojs6.com/290)_",
"floaty.window/floaty.rawWindow 同时支持主线程和子线程执行"
]
},
diff --git a/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java b/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java
index 160dc4e2..8711bd69 100644
--- a/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java
+++ b/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java
@@ -37,6 +37,7 @@ import java.util.List;
*
Structure is described to the parser by providing a class annotated with {@link Asn1Class},
* containing fields annotated with {@link Asn1Field}.
*/
+@SuppressWarnings("deprecation")
public final class Asn1BerParser {
private Asn1BerParser() {}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index c1bce132..77263b79 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -53,8 +53,18 @@
-keep class org.mozilla.javascript.** { *; }
-keep class org.autojs.autojs.core.automator.** { *; }
-keep class org.autojs.autojs.** { *; }
+
+-keep class com.stardust.automator.** { *; }
+-keep class com.stardust.autojs.** { *; }
+-dontwarn com.stardust.**
+
-keepattributes *Annotation*,SourceFile,LineNumberTable
+-keepclassmembers class ** {
+ @org.autojs.autojs.annotation.ScriptInterface ;
+ @com.stardust.autojs.annotation.ScriptInterface ;
+}
+
# Event bus
-keep class org.greenrobot.eventbus.** { *; }
@@ -65,10 +75,6 @@
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
--keepclassmembers class ** {
- @org.autojs.autojs.annotation.ScriptInterface ;
-}
-
# gson
-keep class * extends org.json.JSONObject {
diff --git a/app/src/main/java/com/stardust/app/AppOps.kt b/app/src/main/java/com/stardust/app/AppOps.kt
new file mode 100644
index 00000000..7d3d070d
--- /dev/null
+++ b/app/src/main/java/com/stardust/app/AppOps.kt
@@ -0,0 +1,16 @@
+package com.stardust.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.PackageManager
+
+fun Context.isOpPermissionGranted(permission: String): Boolean {
+ val appOps = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+ @Suppress("DEPRECATION") val mode = appOps.checkOpNoThrow(permission, android.os.Process.myUid(), packageName)
+
+ return if (mode == AppOpsManager.MODE_DEFAULT) {
+ checkCallingOrSelfPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) == PackageManager.PERMISSION_GRANTED
+ } else {
+ mode == AppOpsManager.MODE_ALLOWED
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stardust/app/DialogUtils.java b/app/src/main/java/com/stardust/app/DialogUtils.java
new file mode 100644
index 00000000..aba1bf97
--- /dev/null
+++ b/app/src/main/java/com/stardust/app/DialogUtils.java
@@ -0,0 +1,54 @@
+package com.stardust.app;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.Build;
+import android.os.Looper;
+import android.view.Window;
+import android.view.WindowManager;
+
+/**
+ * Created by Stardust on 2017/8/4.
+ */
+
+public class DialogUtils {
+
+ public static T showDialog(final T dialog) {
+ Context context = dialog.getContext();
+
+ if (!isActivityContext(context)) {
+ Window window = dialog.getWindow();
+ int type;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ } else {
+ type = WindowManager.LayoutParams.TYPE_PHONE;
+ }
+ if (window != null)
+ window.setType(type);
+ }
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ dialog.show();
+ } else {
+ GlobalAppContext.post(new Runnable() {
+ @Override
+ public void run() {
+ dialog.show();
+ }
+ });
+ }
+ return dialog;
+ }
+
+
+ public static boolean isActivityContext(Context context) {
+ if (context instanceof Activity)
+ return true;
+ if (context instanceof ContextWrapper) {
+ return isActivityContext(((ContextWrapper) context).getBaseContext());
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/stardust/app/Fragment.java b/app/src/main/java/com/stardust/app/Fragment.java
new file mode 100644
index 00000000..d6adbe22
--- /dev/null
+++ b/app/src/main/java/com/stardust/app/Fragment.java
@@ -0,0 +1,48 @@
+package com.stardust.app;
+
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.stardust.util.ViewUtil;
+
+/**
+ * Created by Stardust on 2017/1/30.
+ */
+
+public abstract class Fragment extends androidx.fragment.app.Fragment {
+
+ private View mView;
+
+ @NonNull
+ public View getView() {
+ return mView;
+ }
+
+ public T $(int id) {
+ return ViewUtil.$(mView, id);
+ }
+
+ public View findViewById(int id) {
+ return mView.findViewById(id);
+ }
+
+ public View getActivityContentView() {
+ return getActivity().getWindow().getDecorView();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ mView = createView(inflater, container, savedInstanceState);
+ return mView;
+ }
+
+ @Nullable
+ public abstract View createView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState);
+
+
+}
diff --git a/app/src/main/java/com/stardust/app/FragmentPagerAdapterBuilder.java b/app/src/main/java/com/stardust/app/FragmentPagerAdapterBuilder.java
new file mode 100644
index 00000000..68412685
--- /dev/null
+++ b/app/src/main/java/com/stardust/app/FragmentPagerAdapterBuilder.java
@@ -0,0 +1,94 @@
+package com.stardust.app;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
+import android.util.SparseArray;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Stardust on 2017/3/24.
+ */
+
+public class FragmentPagerAdapterBuilder {
+
+ public interface OnFragmentInstantiateListener {
+ void OnInstantiate(int pos, Fragment fragment);
+ }
+
+ private List mFragments = new ArrayList<>();
+ private List mTitles = new ArrayList<>();
+ private FragmentActivity mActivity;
+
+ public FragmentPagerAdapterBuilder(FragmentActivity activity) {
+ mActivity = activity;
+ }
+
+ public FragmentPagerAdapterBuilder add(Fragment fragment, String title) {
+ mFragments.add(fragment);
+ mTitles.add(title);
+ return this;
+ }
+
+ public FragmentPagerAdapterBuilder add(Fragment fragment, int titleResId) {
+ return add(fragment, mActivity.getString(titleResId));
+ }
+
+ public StoredFragmentPagerAdapter build() {
+ return new StoredFragmentPagerAdapter(mActivity.getSupportFragmentManager()) {
+ @Override
+ public Fragment getItem(int position) {
+ return mFragments.get(position);
+ }
+
+ @Override
+ public int getCount() {
+ return mFragments.size();
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mTitles.get(position);
+ }
+ };
+ }
+
+ public abstract static class StoredFragmentPagerAdapter extends FragmentPagerAdapter {
+
+ private SparseArray mStoredFragments = new SparseArray<>();
+ private OnFragmentInstantiateListener mOnFragmentInstantiateListener;
+
+ public StoredFragmentPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ Fragment fragment = (Fragment) super.instantiateItem(container, position);
+ mStoredFragments.put(position, fragment);
+ if(mOnFragmentInstantiateListener != null){
+ mOnFragmentInstantiateListener.OnInstantiate(position, fragment);
+ }
+ return fragment;
+ }
+
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ mStoredFragments.remove(position);
+ super.destroyItem(container, position, object);
+ }
+
+ public Fragment getStoredFragment(int position) {
+ return mStoredFragments.get(position);
+ }
+
+ public void setOnFragmentInstantiateListener(OnFragmentInstantiateListener onFragmentInstantiateListener) {
+ mOnFragmentInstantiateListener = onFragmentInstantiateListener;
+ }
+ }
+}
diff --git a/app/src/main/java/com/stardust/app/GlobalAppContext.java b/app/src/main/java/com/stardust/app/GlobalAppContext.java
new file mode 100644
index 00000000..4b5391a8
--- /dev/null
+++ b/app/src/main/java/com/stardust/app/GlobalAppContext.java
@@ -0,0 +1,92 @@
+package com.stardust.app;
+
+import android.annotation.SuppressLint;
+import android.app.Application;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import androidx.annotation.RequiresApi;
+import android.widget.Toast;
+
+/**
+ * Created by Stardust on 2018/3/22.
+ */
+
+public class GlobalAppContext {
+
+ @SuppressLint("StaticFieldLeak")
+ private static Context sApplicationContext;
+ private static Handler sHandler;
+
+ public static void set(Application a) {
+ sHandler = new Handler(Looper.getMainLooper());
+ sApplicationContext = a.getApplicationContext();
+ }
+
+ public static Context get() {
+ if (sApplicationContext == null)
+ throw new IllegalStateException("Call GlobalAppContext.set() to set a application context");
+ return sApplicationContext;
+ }
+
+ public static String getString(int resId) {
+ return get().getString(resId);
+ }
+
+ public static String getString(int resId, Object... formatArgs) {
+ return get().getString(resId, formatArgs);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ public static int getColor(int id) {
+ return get().getColor(id);
+ }
+
+ public static void toast(final String message) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ Toast.makeText(get(), message, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ sHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(get(), message, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ public static void toast(final int resId) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ Toast.makeText(get(), resId, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ sHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(get(), resId, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ public static void toast(final int resId, final Object... args) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ Toast.makeText(get(), getString(resId, args), Toast.LENGTH_SHORT).show();
+ return;
+ }
+ sHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(get(), getString(resId, args), Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ public static void post(Runnable r) {
+ sHandler.post(r);
+ }
+
+ public static void postDelayed(Runnable r, long m) {
+ sHandler.postDelayed(r, m);
+ }
+}
diff --git a/app/src/main/java/com/stardust/app/MenuUtils.java b/app/src/main/java/com/stardust/app/MenuUtils.java
new file mode 100644
index 00000000..6b590a53
--- /dev/null
+++ b/app/src/main/java/com/stardust/app/MenuUtils.java
@@ -0,0 +1,24 @@
+package com.stardust.app;
+
+import android.graphics.drawable.Drawable;
+import androidx.core.graphics.drawable.DrawableCompat;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+
+public class MenuUtils {
+
+ public static void setMenuIconColor(Menu menu, int color) {
+ for (int i = 0; i < menu.size(); i++) {
+ MenuItem item = menu.getItem(i);
+ Drawable icon = item.getIcon();
+ if (icon != null) {
+ DrawableCompat.setTint(icon, color);
+ }
+ SubMenu subMenu = item.getSubMenu();
+ if (subMenu != null) {
+ setMenuIconColor(subMenu, color);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java b/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java
new file mode 100644
index 00000000..a8017895
--- /dev/null
+++ b/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java
@@ -0,0 +1,53 @@
+package com.stardust.app;
+
+import android.content.Intent;
+import androidx.annotation.NonNull;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Stardust on 2017/3/5.
+ */
+
+public interface OnActivityResultDelegate {
+
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+
+ interface DelegateHost {
+ @NonNull
+ Mediator getOnActivityResultDelegateMediator();
+ }
+
+ class Mediator implements OnActivityResultDelegate {
+
+ private SparseArray mSpecialDelegate = new SparseArray<>();
+ private List mDelegates = new ArrayList<>();
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ OnActivityResultDelegate delegate = mSpecialDelegate.get(requestCode);
+ if (delegate != null) {
+ delegate.onActivityResult(requestCode, resultCode, data);
+ }
+ for (OnActivityResultDelegate d : mDelegates) {
+ d.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ public void addDelegate(OnActivityResultDelegate delegate) {
+ mDelegates.add(delegate);
+ }
+
+ public void addDelegate(int requestCode, OnActivityResultDelegate delegate) {
+ mSpecialDelegate.put(requestCode, delegate);
+ }
+
+ public void removeDelegate(OnActivityResultDelegate delegate) {
+ if (mDelegates.remove(delegate)) {
+ mSpecialDelegate.removeAt(mSpecialDelegate.indexOfValue(delegate));
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/stardust/app/SimpleActivityLifecycleCallbacks.java b/app/src/main/java/com/stardust/app/SimpleActivityLifecycleCallbacks.java
new file mode 100644
index 00000000..ddf1e8fb
--- /dev/null
+++ b/app/src/main/java/com/stardust/app/SimpleActivityLifecycleCallbacks.java
@@ -0,0 +1,47 @@
+package com.stardust.app;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+/**
+ * Created by Stardust on 2017/4/2.
+ */
+
+public class SimpleActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stardust/autojs/AutoJs.java b/app/src/main/java/com/stardust/autojs/AutoJs.java
new file mode 100644
index 00000000..d65b7780
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/AutoJs.java
@@ -0,0 +1,267 @@
+package com.stardust.autojs;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import com.stardust.app.OnActivityResultDelegate;
+import com.stardust.app.SimpleActivityLifecycleCallbacks;
+import com.stardust.autojs.core.accessibility.AccessibilityBridge;
+import com.stardust.autojs.core.console.GlobalConsole;
+import com.stardust.autojs.core.console.ConsoleImpl;
+import com.stardust.autojs.core.image.capture.ScreenCaptureRequestActivity;
+import com.stardust.autojs.core.image.capture.ScreenCaptureRequester;
+import com.stardust.autojs.core.record.accessibility.AccessibilityActionRecorder;
+import com.stardust.autojs.core.util.Shell;
+import com.stardust.autojs.engine.LoopBasedJavaScriptEngine;
+import com.stardust.autojs.engine.RootAutomatorEngine;
+import com.stardust.autojs.engine.ScriptEngineManager;
+import com.stardust.autojs.rhino.InterruptibleAndroidContextFactory;
+import com.stardust.autojs.runtime.ScriptRuntime;
+import com.stardust.autojs.runtime.accessibility.AccessibilityConfig;
+import com.stardust.autojs.runtime.api.AppUtils;
+import com.stardust.autojs.script.AutoFileSource;
+import com.stardust.autojs.script.JavaScriptSource;
+import com.stardust.util.ResourceMonitor;
+import com.stardust.util.ScreenMetrics;
+import com.stardust.util.UiHandler;
+import com.stardust.autojs.core.activity.ActivityInfoProvider;
+import com.stardust.view.accessibility.AccessibilityNotificationObserver;
+import com.stardust.view.accessibility.AccessibilityService;
+import com.stardust.view.accessibility.LayoutInspector;
+
+import org.mozilla.javascript.ContextFactory;
+import org.mozilla.javascript.WrappedException;
+
+import java.io.File;
+
+/**
+ * Created by Stardust on 2017/11/29.
+ */
+
+public abstract class AutoJs {
+
+ private final AccessibilityActionRecorder mAccessibilityActionRecorder = new AccessibilityActionRecorder();
+ private final AccessibilityNotificationObserver mNotificationObserver;
+ private ScriptEngineManager mScriptEngineManager;
+ private final LayoutInspector mLayoutInspector;
+ private final Context mContext;
+ private final Application mApplication;
+ private final UiHandler mUiHandler;
+ private final AppUtils mAppUtils;
+ private final ActivityInfoProvider mActivityInfoProvider;
+ private final ScreenCaptureRequester mScreenCaptureRequester = new ScreenCaptureRequesterImpl();
+ private final ScriptEngineService mScriptEngineService;
+ private final GlobalConsole mGlobalConsole;
+
+
+ protected AutoJs(final Application application) {
+ mContext = application.getApplicationContext();
+ mApplication = application;
+ mLayoutInspector = new LayoutInspector(mContext);
+ mUiHandler = new UiHandler(mContext);
+ mAppUtils = createAppUtils(mContext);
+ mGlobalConsole = createGlobalConsole();
+ mNotificationObserver = new AccessibilityNotificationObserver(mContext);
+ mActivityInfoProvider = new ActivityInfoProvider(mContext);
+ mScriptEngineService = buildScriptEngineService();
+ ScriptEngineService.setInstance(mScriptEngineService);
+ init();
+ }
+
+ protected AppUtils createAppUtils(Context context) {
+ return new AppUtils(mContext);
+ }
+
+ protected GlobalConsole createGlobalConsole() {
+ return new GlobalConsole(mUiHandler);
+ }
+
+ protected void init() {
+ addAccessibilityServiceDelegates();
+ registerActivityLifecycleCallbacks();
+ ResourceMonitor.setExceptionCreator(resource -> {
+ Exception exception;
+ if (org.mozilla.javascript.Context.getCurrentContext() != null) {
+ exception = new WrappedException(new ResourceMonitor.UnclosedResourceException(resource));
+ } else {
+ exception = new ResourceMonitor.UnclosedResourceException(resource);
+ }
+ exception.fillInStackTrace();
+ return exception;
+ });
+ ResourceMonitor.setUnclosedResourceDetectedHandler(detectedException -> mGlobalConsole.error(detectedException));
+ }
+
+ public abstract void ensureAccessibilityServiceEnabled();
+
+ protected Application getApplication() {
+ return mApplication;
+ }
+
+ public ScriptEngineManager getScriptEngineManager() {
+ return mScriptEngineManager;
+ }
+
+ protected ScriptEngineService buildScriptEngineService() {
+ initScriptEngineManager();
+ return new ScriptEngineServiceBuilder()
+ .uiHandler(mUiHandler)
+ .globalConsole(mGlobalConsole)
+ .engineManger(mScriptEngineManager)
+ .build();
+ }
+
+ protected void initScriptEngineManager() {
+ mScriptEngineManager = new ScriptEngineManager(mContext);
+ mScriptEngineManager.registerEngine(JavaScriptSource.ENGINE, () -> {
+ LoopBasedJavaScriptEngine engine = new LoopBasedJavaScriptEngine(mContext);
+ engine.setRuntime(createRuntime());
+ return engine;
+ });
+ initContextFactory();
+ mScriptEngineManager.registerEngine(AutoFileSource.ENGINE, () -> new RootAutomatorEngine(mContext));
+ }
+
+ protected void initContextFactory() {
+ ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(mContext.getCacheDir(), "classes")));
+ }
+
+ protected ScriptRuntime createRuntime() {
+ return new ScriptRuntime.Builder()
+ .setConsole(new ConsoleImpl(mUiHandler, mGlobalConsole))
+ .setScreenCaptureRequester(mScreenCaptureRequester)
+ .setAccessibilityBridge(new AccessibilityBridgeImpl(mUiHandler))
+ .setUiHandler(mUiHandler)
+ .setAppUtils(mAppUtils)
+ .setEngineService(mScriptEngineService)
+ .setShellSupplier(() -> new Shell(mContext, true)).build();
+ }
+
+ protected void registerActivityLifecycleCallbacks() {
+ getApplication().registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks() {
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ ScreenMetrics.initIfNeeded(activity);
+ mAppUtils.setCurrentActivity(activity);
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ mAppUtils.setCurrentActivity(null);
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ mAppUtils.setCurrentActivity(activity);
+ }
+ });
+ }
+
+
+ private void addAccessibilityServiceDelegates() {
+ AccessibilityService.Companion.addDelegate(100, mActivityInfoProvider);
+ AccessibilityService.Companion.addDelegate(200, mNotificationObserver);
+ AccessibilityService.Companion.addDelegate(300, mAccessibilityActionRecorder);
+ }
+
+ public AccessibilityActionRecorder getAccessibilityActionRecorder() {
+ return mAccessibilityActionRecorder;
+ }
+
+ public AppUtils getAppUtils() {
+ return mAppUtils;
+ }
+
+ public UiHandler getUiHandler() {
+ return mUiHandler;
+ }
+
+ public LayoutInspector getLayoutInspector() {
+ return mLayoutInspector;
+ }
+
+ public GlobalConsole getGlobalConsole() {
+ return mGlobalConsole;
+ }
+
+ public ScriptEngineService getScriptEngineService() {
+ return mScriptEngineService;
+ }
+
+ public ActivityInfoProvider getInfoProvider() {
+ return mActivityInfoProvider;
+ }
+
+
+ public abstract void waitForAccessibilityServiceEnabled();
+
+ protected AccessibilityConfig createAccessibilityConfig() {
+ return new AccessibilityConfig();
+ }
+
+ private class AccessibilityBridgeImpl extends AccessibilityBridge {
+
+ public AccessibilityBridgeImpl(UiHandler uiHandler) {
+ super(mContext, createAccessibilityConfig(), uiHandler);
+ }
+
+ @Override
+ public void ensureServiceEnabled() {
+ AutoJs.this.ensureAccessibilityServiceEnabled();
+ }
+
+ @Override
+ public void waitForServiceEnabled() {
+ AutoJs.this.waitForAccessibilityServiceEnabled();
+ }
+
+ @Nullable
+ @Override
+ public AccessibilityService getService() {
+ return AccessibilityService.Companion.getInstance();
+ }
+
+ @Override
+ public ActivityInfoProvider getInfoProvider() {
+ return mActivityInfoProvider;
+ }
+
+ @Override
+ public AccessibilityNotificationObserver getNotificationObserver() {
+ return mNotificationObserver;
+ }
+
+ }
+
+ private class ScreenCaptureRequesterImpl extends ScreenCaptureRequester.AbstractScreenCaptureRequester {
+
+ @Override
+ public void setOnActivityResultCallback(Callback callback) {
+ super.setOnActivityResultCallback((result, data) -> {
+ mResult = data;
+ callback.onRequestResult(result, data);
+ });
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void request() {
+ Activity activity = mAppUtils.getCurrentActivity();
+ if (activity instanceof OnActivityResultDelegate.DelegateHost) {
+ ScreenCaptureRequester requester = new ActivityScreenCaptureRequester(
+ ((OnActivityResultDelegate.DelegateHost) activity).getOnActivityResultDelegateMediator(), activity);
+ requester.setOnActivityResultCallback(mCallback);
+ requester.request();
+ } else {
+ ScreenCaptureRequestActivity.request(mContext, mCallback);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/stardust/autojs/BuildConfig.kt b/app/src/main/java/com/stardust/autojs/BuildConfig.kt
new file mode 100644
index 00000000..3092f8ba
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/BuildConfig.kt
@@ -0,0 +1,3 @@
+package com.stardust.autojs
+
+typealias BuildConfig = org.autojs.autojs6.BuildConfig
diff --git a/app/src/main/java/com/stardust/autojs/Config.java b/app/src/main/java/com/stardust/autojs/Config.java
new file mode 100644
index 00000000..ef102db4
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/Config.java
@@ -0,0 +1,41 @@
+package com.stardust.autojs;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+/**
+ * Created by Stardust on 2017/12/8.
+ */
+
+public class Config {
+
+ private static Config sInstance;
+ private SharedPreferences mSharedPreferences;
+ private final Context mContext;
+
+ public Config(Context context) {
+ mContext = context;
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ public static void setInstance(Config instance) {
+ if (sInstance != null)
+ throw new IllegalStateException();
+ sInstance = instance;
+ }
+
+ public static Config getInstance() {
+ return sInstance;
+ }
+
+ public boolean isPrintJavaStackTraceEnabled() {
+ return mSharedPreferences.getBoolean("key_print_java_stack_trace", false);
+ }
+
+ private String getString(int resId) {
+ return mContext.getString(resId);
+ }
+
+
+}
diff --git a/app/src/main/java/com/stardust/autojs/ScriptEngineService.java b/app/src/main/java/com/stardust/autojs/ScriptEngineService.java
new file mode 100644
index 00000000..98874058
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/ScriptEngineService.java
@@ -0,0 +1,286 @@
+package com.stardust.autojs;
+
+import android.content.Context;
+import androidx.annotation.Nullable;
+
+import com.stardust.autojs.engine.JavaScriptEngine;
+import com.stardust.autojs.engine.ScriptEngine;
+import com.stardust.autojs.engine.ScriptEngineManager;
+import com.stardust.autojs.execution.ExecutionConfig;
+import com.stardust.autojs.execution.LoopedBasedJavaScriptExecution;
+import com.stardust.autojs.execution.RunnableScriptExecution;
+import com.stardust.autojs.execution.ScriptExecuteActivity;
+import com.stardust.autojs.execution.ScriptExecution;
+import com.stardust.autojs.execution.ScriptExecutionListener;
+import com.stardust.autojs.execution.ScriptExecutionObserver;
+import com.stardust.autojs.execution.ScriptExecutionTask;
+import com.stardust.autojs.execution.SimpleScriptExecutionListener;
+import com.stardust.autojs.runtime.ScriptRuntime;
+import com.stardust.autojs.runtime.api.Console;
+import com.stardust.autojs.script.JavaScriptSource;
+import com.stardust.autojs.script.ScriptSource;
+import com.stardust.lang.ThreadCompat;
+import com.stardust.util.UiHandler;
+
+import org.autojs.autojs.util.ViewUtils;
+import org.autojs.autojs6.R;
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static com.stardust.autojs.runtime.exception.ScriptInterruptedException.causedByInterrupted;
+
+/**
+ * Created by Stardust on 2017/1/23.
+ */
+
+public class ScriptEngineService {
+
+ private static final String LOG_TAG = "ScriptEngineService";
+ private static final EventBus EVENT_BUS = new EventBus();
+ private static final ScriptExecutionListener GLOBAL_LISTENER = new SimpleScriptExecutionListener() {
+ @Override
+ public void onStart(ScriptExecution execution) {
+ if (execution.getEngine() instanceof JavaScriptEngine) {
+ ((JavaScriptEngine) execution.getEngine()).getRuntime()
+ .console.setTitle(execution.getSource().getName());
+ }
+ EVENT_BUS.post(new ScriptExecutionEvent(ScriptExecutionEvent.ON_START, execution.getSource().toString()));
+ }
+
+ @Override
+ public void onSuccess(ScriptExecution execution, Object result) {
+ onFinish(execution);
+ }
+
+ private void onFinish(ScriptExecution execution) {
+
+ }
+
+ @Override
+ public void onException(ScriptExecution execution, Throwable e) {
+ e.printStackTrace();
+ onFinish(execution);
+ String message = null;
+ if (!causedByInterrupted(e)) {
+ message = e.getMessage();
+ if (execution.getEngine() instanceof JavaScriptEngine) {
+ ((JavaScriptEngine) execution.getEngine()).getRuntime()
+ .console.error(e);
+ }
+ }
+ if (execution.getEngine() instanceof JavaScriptEngine) {
+ JavaScriptEngine engine = (JavaScriptEngine) execution.getEngine();
+ Throwable uncaughtException = engine.getUncaughtException();
+ if (uncaughtException != null) {
+ engine.getRuntime().console.error(uncaughtException);
+ message = uncaughtException.getMessage();
+ }
+ }
+ if (message != null) {
+ EVENT_BUS.post(new ScriptExecutionEvent(ScriptExecutionEvent.ON_EXCEPTION, message));
+ }
+ }
+
+ };
+
+
+ private static ScriptEngineService sInstance;
+ private final Context mContext;
+ private UiHandler mUiHandler;
+ private final Console mGlobalConsole;
+ private final ScriptEngineManager mScriptEngineManager;
+ private final EngineLifecycleObserver mEngineLifecycleObserver = new EngineLifecycleObserver() {
+
+ @Override
+ public void onEngineRemove(ScriptEngine engine) {
+ mScriptExecutions.remove(engine.getId());
+ super.onEngineRemove(engine);
+ }
+ };
+ private ScriptExecutionObserver mScriptExecutionObserver = new ScriptExecutionObserver();
+ private LinkedHashMap mScriptExecutions = new LinkedHashMap<>();
+
+ ScriptEngineService(ScriptEngineServiceBuilder builder) {
+ mUiHandler = builder.mUiHandler;
+ mContext = mUiHandler.getContext();
+ mScriptEngineManager = builder.mScriptEngineManager;
+ mGlobalConsole = builder.mGlobalConsole;
+ mScriptEngineManager.setEngineLifecycleCallback(mEngineLifecycleObserver);
+ mScriptExecutionObserver.registerScriptExecutionListener(GLOBAL_LISTENER);
+ EVENT_BUS.register(this);
+ mScriptEngineManager.putGlobal("context", mUiHandler.getContext());
+ ScriptRuntime.setApplicationContext(builder.mUiHandler.getContext().getApplicationContext());
+ }
+
+ public Console getGlobalConsole() {
+ return mGlobalConsole;
+ }
+
+ public void registerEngineLifecycleCallback(ScriptEngineManager.EngineLifecycleCallback engineLifecycleCallback) {
+ mEngineLifecycleObserver.registerCallback(engineLifecycleCallback);
+ }
+
+ public void unregisterEngineLifecycleCallback(ScriptEngineManager.EngineLifecycleCallback engineLifecycleCallback) {
+ mEngineLifecycleObserver.unregisterCallback(engineLifecycleCallback);
+ }
+
+ public boolean registerGlobalScriptExecutionListener(ScriptExecutionListener listener) {
+ return mScriptExecutionObserver.registerScriptExecutionListener(listener);
+ }
+
+ public boolean unregisterGlobalScriptExecutionListener(ScriptExecutionListener listener) {
+ return mScriptExecutionObserver.removeScriptExecutionListener(listener);
+ }
+
+ public ScriptExecution execute(ScriptExecutionTask task) {
+ ScriptExecution execution = executeInternal(task);
+ mScriptExecutions.put(execution.getId(), execution);
+ return execution;
+ }
+
+ private ScriptExecution executeInternal(ScriptExecutionTask task) {
+ if (task.getListener() != null) {
+ task.setExecutionListener(new ScriptExecutionObserver.Wrapper(mScriptExecutionObserver, task.getListener()));
+ } else {
+ task.setExecutionListener(mScriptExecutionObserver);
+ }
+ ScriptSource source = task.getSource();
+ if (source instanceof JavaScriptSource) {
+ int mode = ((JavaScriptSource) source).getExecutionMode();
+ if ((mode & JavaScriptSource.EXECUTION_MODE_UI) != 0) {
+ return ScriptExecuteActivity.execute(mContext, mScriptEngineManager, task);
+ }
+ }
+ RunnableScriptExecution r;
+ if (source instanceof JavaScriptSource) {
+ r = new LoopedBasedJavaScriptExecution(mScriptEngineManager, task);
+ } else {
+ r = new RunnableScriptExecution(mScriptEngineManager, task);
+ }
+ new ThreadCompat(r).start();
+ return r;
+ }
+
+ public ScriptExecution execute(ScriptSource source, ScriptExecutionListener listener, ExecutionConfig config) {
+ return execute(new ScriptExecutionTask(source, listener, config));
+ }
+
+ public ScriptExecution execute(ScriptSource source, ExecutionConfig config) {
+ return execute(new ScriptExecutionTask(source, null, config));
+ }
+
+ @Subscribe
+ public void onScriptExecution(ScriptExecutionEvent event) {
+ if (event.getCode() == ScriptExecutionEvent.ON_START) {
+ mGlobalConsole.verbose(mContext.getString(R.string.text_start_running) + "[" + event.getMessage() + "]");
+ } else if (event.getCode() == ScriptExecutionEvent.ON_EXCEPTION) {
+ mUiHandler.toast(mContext.getString(R.string.text_error) + ": " + event.getMessage());
+ }
+ }
+
+ public int stopAll() {
+ return mScriptEngineManager.stopAll();
+ }
+
+
+ public void stopAllAndToast() {
+ int n = stopAll();
+ if (n > 0)
+ // mUiHandler.toast(String.format(mContext.getString(R.string.text_already_stop_n_scripts), n));
+ ViewUtils.showToast(mContext, mContext.getResources().getQuantityString(R.plurals.text_already_stop_n_scripts, n, n));
+ }
+
+ public Set getEngines() {
+ return mScriptEngineManager.getEngines();
+ }
+
+ public Collection getScriptExecutions() {
+ return mScriptExecutions.values();
+ }
+
+ @Nullable
+ public ScriptExecution getScriptExecution(int id) {
+ if (id == ScriptExecution.NO_ID) {
+ return null;
+ }
+ return mScriptExecutions.get(id);
+ }
+
+ public static void setInstance(ScriptEngineService service) {
+ if (sInstance != null) {
+ throw new IllegalStateException();
+ }
+ sInstance = service;
+ }
+
+ public static ScriptEngineService getInstance() {
+ return sInstance;
+ }
+
+
+ private static class EngineLifecycleObserver implements ScriptEngineManager.EngineLifecycleCallback {
+
+ private final Set mEngineLifecycleCallbacks = new LinkedHashSet<>();
+
+ @Override
+ public void onEngineCreate(ScriptEngine engine) {
+ synchronized (mEngineLifecycleCallbacks) {
+ for (ScriptEngineManager.EngineLifecycleCallback callback : mEngineLifecycleCallbacks) {
+ callback.onEngineCreate(engine);
+ }
+ }
+ }
+
+ @Override
+ public void onEngineRemove(ScriptEngine engine) {
+ synchronized (mEngineLifecycleCallbacks) {
+ for (ScriptEngineManager.EngineLifecycleCallback callback : mEngineLifecycleCallbacks) {
+ callback.onEngineRemove(engine);
+ }
+ }
+ }
+
+ void registerCallback(ScriptEngineManager.EngineLifecycleCallback callback) {
+ synchronized (mEngineLifecycleCallbacks) {
+ mEngineLifecycleCallbacks.add(callback);
+ }
+
+ }
+
+ void unregisterCallback(ScriptEngineManager.EngineLifecycleCallback callback) {
+ synchronized (mEngineLifecycleCallbacks) {
+ mEngineLifecycleCallbacks.remove(callback);
+ }
+ }
+ }
+
+
+ private static class ScriptExecutionEvent {
+
+ static final int ON_START = 1001;
+ static final int ON_SUCCESS = 1002;
+ static final int ON_EXCEPTION = 1003;
+
+ private final int mCode;
+ private final String mMessage;
+
+ ScriptExecutionEvent(int code, String message) {
+ mCode = code;
+ mMessage = message;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/stardust/autojs/ScriptEngineServiceBuilder.java b/app/src/main/java/com/stardust/autojs/ScriptEngineServiceBuilder.java
new file mode 100644
index 00000000..1fa96f2a
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/ScriptEngineServiceBuilder.java
@@ -0,0 +1,43 @@
+package com.stardust.autojs;
+
+import com.stardust.autojs.engine.ScriptEngineManager;
+import com.stardust.autojs.runtime.ScriptRuntime;
+import com.stardust.autojs.runtime.api.Console;
+import com.stardust.util.Supplier;
+import com.stardust.util.UiHandler;
+
+/**
+ * Created by Stardust on 2017/4/2.
+ */
+
+public class ScriptEngineServiceBuilder {
+
+ ScriptEngineManager mScriptEngineManager;
+ Console mGlobalConsole;
+ UiHandler mUiHandler;
+
+ public ScriptEngineServiceBuilder() {
+
+ }
+
+ public ScriptEngineServiceBuilder uiHandler(UiHandler uiHandler) {
+ mUiHandler = uiHandler;
+ return this;
+ }
+
+ public ScriptEngineServiceBuilder engineManger(ScriptEngineManager manager) {
+ mScriptEngineManager = manager;
+ return this;
+ }
+
+ public ScriptEngineServiceBuilder globalConsole(Console console) {
+ mGlobalConsole = console;
+ return this;
+ }
+
+ public ScriptEngineService build() {
+ return new ScriptEngineService(this);
+ }
+
+
+}
diff --git a/app/src/main/java/com/stardust/autojs/annotation/ScriptClass.java b/app/src/main/java/com/stardust/autojs/annotation/ScriptClass.java
new file mode 100644
index 00000000..aaab5ba3
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/annotation/ScriptClass.java
@@ -0,0 +1,14 @@
+package com.stardust.autojs.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Stardust on 2017/4/3.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface ScriptClass {
+}
diff --git a/app/src/main/java/com/stardust/autojs/annotation/ScriptInterface.java b/app/src/main/java/com/stardust/autojs/annotation/ScriptInterface.java
new file mode 100644
index 00000000..80da3e29
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/annotation/ScriptInterface.java
@@ -0,0 +1,14 @@
+package com.stardust.autojs.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Stardust on 2017/4/2.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.METHOD})
+public @interface ScriptInterface {
+}
diff --git a/app/src/main/java/com/stardust/autojs/annotation/ScriptVariable.java b/app/src/main/java/com/stardust/autojs/annotation/ScriptVariable.java
new file mode 100644
index 00000000..977937cc
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/annotation/ScriptVariable.java
@@ -0,0 +1,15 @@
+package com.stardust.autojs.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Stardust on 2017/4/2.
+ */
+
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.FIELD})
+public @interface ScriptVariable {
+}
diff --git a/app/src/main/java/com/stardust/autojs/codegeneration/CodeGenerator.java b/app/src/main/java/com/stardust/autojs/codegeneration/CodeGenerator.java
new file mode 100644
index 00000000..2b00d410
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/codegeneration/CodeGenerator.java
@@ -0,0 +1,205 @@
+package com.stardust.autojs.codegeneration;
+
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.stardust.automator.UiGlobalSelector;
+import com.stardust.automator.UiObject;
+import com.stardust.view.accessibility.NodeInfo;
+
+/**
+ * Created by Stardust on 2017/12/7.
+ */
+
+public class CodeGenerator {
+
+ public static final int UNTIL_FIND = 0;
+ public static final int FIND_ONE = 1;
+ public static final int WAIT_FOR = 2;
+ public static final int EXISTS = 3;
+
+ private final ReadOnlyUiObject mRoot;
+ private final ReadOnlyUiObject mTarget;
+ private boolean mUsingId = true;
+ private boolean mUsingDesc = true;
+ private boolean mUsingText = true;
+ private int mSearchMode = FIND_ONE;
+ private int mAction = -1;
+
+ public CodeGenerator(NodeInfo root, NodeInfo target) {
+ this(new ReadOnlyUiObject(root), new ReadOnlyUiObject(target));
+ }
+
+ public CodeGenerator(ReadOnlyUiObject root, ReadOnlyUiObject target) {
+ mRoot = root;
+ mTarget = target;
+ }
+
+
+ public void setUsingId(boolean usingId) {
+ mUsingId = usingId;
+ }
+
+ public void setUsingDesc(boolean usingDesc) {
+ mUsingDesc = usingDesc;
+ }
+
+ public void setUsingText(boolean usingText) {
+ mUsingText = usingText;
+ }
+
+ public void setSearchMode(int searchMode) {
+ mSearchMode = searchMode;
+ }
+
+ public void setAction(int action) {
+ mAction = action;
+ }
+
+ public String generateCode() {
+ UiObject collection = getCollectionParent(mTarget);
+ if (collection != null) {
+ return generateCodeForCollectionChild(collection, mTarget);
+ }
+
+ UiSelectorGenerator generator = new UiSelectorGenerator(mRoot, mTarget);
+ generator.setSearchMode(mSearchMode);
+ generator.setUsingDesc(mUsingDesc);
+ generator.setUsingId(mUsingId);
+ generator.setUsingText(mUsingText);
+ String selector = generateCode(generator, mRoot, mTarget, 2, 2, true);
+ if (selector == null)
+ return null;
+ return generateAction(selector);
+ }
+
+
+ protected String generateCode(UiSelectorGenerator generator, UiObject root, UiObject target, int maxParentLevel, int maxChildrenLevel, boolean withFind) {
+ String selector;
+ if (withFind) {
+ selector = generator.generateSelectorCode();
+ } else {
+ UiGlobalSelector s = generator.generateSelector();
+ selector = s == null ? null : s.toString();
+ }
+ if (selector != null) {
+ return selector;
+ }
+
+ if (maxChildrenLevel > 0) {
+ for (int i = 0; i < target.childCount(); i++) {
+ UiObject child = target.child(i);
+ if (child == null)
+ continue;
+ String childCode = generateCode(root, child, 0, maxChildrenLevel - 1);
+ if (childCode != null) {
+ return childCode + ".parent()";
+ }
+ }
+ }
+ if (maxParentLevel > 0 && target.parent() != null) {
+ int index = target.indexInParent();
+ if (index > 0) {
+ String parentCode = generateCode(root, target.parent(), maxParentLevel - 1, 0);
+ if (parentCode != null) {
+ return parentCode + "child(" + index + ")";
+ }
+ }
+ }
+ return null;
+ }
+
+ protected String generateCode(UiObject root, UiObject target, int maxParentLevel, int maxChildrenLevel) {
+ return generateCode(root, target, maxParentLevel, maxChildrenLevel, true);
+ }
+
+ protected String generateCode(UiObject root, UiObject target, int maxParentLevel, int maxChildrenLevel, boolean withFind) {
+ UiSelectorGenerator generator = new UiSelectorGenerator(root, target);
+ generator.setUsingId(mUsingId);
+ return generateCode(generator, root, target, maxParentLevel, maxChildrenLevel, withFind);
+ }
+
+ private String generateAction(String selector) {
+ if (selector == null)
+ return null;
+ if (mSearchMode == WAIT_FOR) {
+ return selector + ".waitFor()";
+ }
+ if (mSearchMode == EXISTS) {
+ return "if(" + selector + ".exists()){\n \n}";
+ }
+ String action = getAction();
+ if (action.isEmpty()) {
+ return selector;
+ } else {
+ return selector + "." + action;
+ }
+ }
+
+ private String getAction() {
+ switch (mAction) {
+ case AccessibilityNodeInfoCompat.ACTION_CLICK:
+ return "click()";
+ case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:
+ return "longClick()";
+
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
+ return "scrollBackward()";
+
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
+ return "scrollForward()";
+
+ case AccessibilityNodeInfoCompat.ACTION_SET_TEXT:
+ return "setText(\"\")";
+ }
+ return "";
+ }
+
+ private String generateCodeForCollectionChild(UiObject collection, UiObject target) {
+ UiObject parent = target.parent();
+ if (parent == null)
+ return null;
+ UiObject collectionItem = null;
+ for (int i = 0; i < collection.childCount(); i++) {
+ if (inherits(collection.child(i), target)) {
+ collectionItem = collection.child(i);
+ break;
+ }
+ }
+ if (collectionItem == null)
+ return null;
+ String collectionCode = generateCode(mRoot, collection, 2, 0);
+ if (collectionCode == null)
+ return null;
+ String itemCode = generateCode(collectionItem, target, 1, 2, false);
+ if (itemCode == null)
+ return null;
+ return collectionCode + ".children().forEach(child => {\n"
+ + "var target = child.findOne(" + itemCode + ");\n"
+ + "target." + getAction() + ";\n"
+ + "});";
+ }
+
+ private boolean inherits(UiObject root, UiObject target) {
+ for (int i = 0; i < root.childCount(); i++) {
+ UiObject child = root.child(i);
+ if (child != null) {
+ if (child.equals(target) || inherits(child, target)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private UiObject getCollectionParent(UiObject target) {
+ UiObject parent = target.parent();
+ while (parent != null) {
+ if (parent.rowCount() > 0 || parent.columnCount() > 0) {
+ return parent;
+ }
+ parent = parent.parent();
+ }
+ return null;
+ }
+
+}
diff --git a/app/src/main/java/com/stardust/autojs/codegeneration/ReadOnlyUiObject.java b/app/src/main/java/com/stardust/autojs/codegeneration/ReadOnlyUiObject.java
new file mode 100644
index 00000000..bd3b2ec5
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/codegeneration/ReadOnlyUiObject.java
@@ -0,0 +1,332 @@
+package com.stardust.autojs.codegeneration;
+
+import android.graphics.Rect;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.stardust.automator.UiObject;
+import com.stardust.view.accessibility.NodeInfo;
+
+/**
+ * Created by Stardust on 2017/11/5.
+ */
+
+public class ReadOnlyUiObject extends UiObject {
+
+ private NodeInfo mNodeInfo;
+
+ public ReadOnlyUiObject(NodeInfo info) {
+ super(null, info.getDepth(), -1);
+ mNodeInfo = info;
+ }
+
+ public ReadOnlyUiObject(NodeInfo info, int indexInParent) {
+ super(null, info.getDepth(), indexInParent);
+ mNodeInfo = info;
+ }
+
+ @Nullable
+ @Override
+ public UiObject child(int i) {
+ return new ReadOnlyUiObject(mNodeInfo.getChildren().get(i), i);
+ }
+
+ @Nullable
+ @Override
+ public UiObject parent() {
+ return mNodeInfo.getParent() == null ? null : new ReadOnlyUiObject(mNodeInfo.getParent());
+ }
+
+ @Override
+ public int childCount() {
+ return mNodeInfo.getChildren().size();
+ }
+
+ @Override
+ public int getChildCount() {
+ return childCount();
+ }
+
+ @Override
+ public String className() {
+ return mNodeInfo.getClassName();
+ }
+
+ @Override
+ public CharSequence getClassName() {
+ return className();
+ }
+
+ @Override
+ public String packageName() {
+ return mNodeInfo.getPackageName();
+ }
+
+ @Override
+ public CharSequence getPackageName() {
+ return packageName();
+ }
+
+ @Override
+ public String id() {
+ return mNodeInfo.getId();
+ }
+
+ @Override
+ public String desc() {
+ return mNodeInfo.getDesc();
+ }
+
+ @Override
+ public String getViewIdResourceName() {
+ return id();
+ }
+
+ @Override
+ public CharSequence getContentDescription() {
+ return desc();
+ }
+
+
+ @Override
+ public Rect bounds() {
+ return mNodeInfo.getBoundsInScreen();
+ }
+
+ @Override
+ public Rect boundsInParent() {
+ return mNodeInfo.getBoundsInParent();
+ }
+
+ @Override
+ public int drawingOrder() {
+ return mNodeInfo.getDrawingOrder();
+ }
+
+ @NonNull
+ @Override
+ public String text() {
+ return mNodeInfo.getText();
+ }
+
+ @Override
+ public CharSequence getText() {
+ return text();
+ }
+
+ @Override
+ public AccessibilityNodeInfoCompat getChild(int index) {
+ return child(index);
+ }
+
+ @Override
+ public int getDrawingOrder() {
+ return drawingOrder();
+ }
+
+ @Override
+ public void getBoundsInParent(Rect outBounds) {
+ outBounds.set(mNodeInfo.getBoundsInParent());
+ }
+
+ @Override
+ public void getBoundsInScreen(Rect outBounds) {
+ outBounds.set(mNodeInfo.getBoundsInScreen());
+ }
+
+
+ @Override
+ public int depth() {
+ return mNodeInfo.getDepth();
+ }
+
+ @Override
+ public boolean checkable() {
+ return mNodeInfo.getCheckable();
+ }
+
+ @Override
+ public boolean checked() {
+ return mNodeInfo.getChecked();
+ }
+
+ @Override
+ public boolean focusable() {
+ return mNodeInfo.getFocusable();
+ }
+
+ @Override
+ public boolean focused() {
+ return mNodeInfo.getFocused();
+ }
+
+ @Override
+ public boolean visibleToUser() {
+ return mNodeInfo.getVisibleToUser();
+ }
+
+ @Override
+ public boolean accessibilityFocused() {
+ return mNodeInfo.getAccessibilityFocused();
+ }
+
+ @Override
+ public boolean selected() {
+ return mNodeInfo.getSelected();
+ }
+
+ @Override
+ public boolean clickable() {
+ return mNodeInfo.getClickable();
+ }
+
+ @Override
+ public boolean longClickable() {
+ return mNodeInfo.getLongClickable();
+ }
+
+ @Override
+ public boolean enabled() {
+ return mNodeInfo.getEnabled();
+ }
+
+ @Override
+ public boolean scrollable() {
+ return mNodeInfo.getScrollable();
+ }
+
+ @Override
+ public boolean isCheckable() {
+ return checkable();
+ }
+
+ @Override
+ public boolean isChecked() {
+ return checked();
+ }
+
+ @Override
+ public boolean isFocusable() {
+ return focusable();
+ }
+
+ @Override
+ public boolean isFocused() {
+ return focused();
+ }
+
+ @Override
+ public boolean isVisibleToUser() {
+ return visibleToUser();
+ }
+
+ @Override
+ public boolean isAccessibilityFocused() {
+ return accessibilityFocused();
+ }
+
+ @Override
+ public boolean isSelected() {
+ return selected();
+ }
+
+ @Override
+ public boolean isClickable() {
+ return clickable();
+ }
+
+ @Override
+ public boolean isLongClickable() {
+ return longClickable();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled();
+ }
+
+ @Override
+ public boolean isPassword() {
+ return password();
+ }
+
+ @Override
+ public boolean isScrollable() {
+ return scrollable();
+ }
+
+
+ @Override
+ public boolean isContextClickable() {
+ return mNodeInfo.getContextClickable();
+ }
+
+ @Override
+ public boolean isDismissable() {
+ return mNodeInfo.getDismissable();
+ }
+
+ @Override
+ public boolean isEditable() {
+ return mNodeInfo.getEditable();
+ }
+
+ @Override
+ public int row() {
+ return mNodeInfo.getRow();
+ }
+
+ @Override
+ public int column() {
+ return mNodeInfo.getColumn();
+ }
+
+ @Override
+ public int rowSpan() {
+ return mNodeInfo.getRowSpan();
+ }
+
+ @Override
+ public int columnSpan() {
+ return mNodeInfo.getColumnSpan();
+ }
+
+ @Override
+ public int rowCount() {
+ return mNodeInfo.getRowCount();
+ }
+
+ @Override
+ public int columnCount() {
+ return mNodeInfo.getColumnCount();
+ }
+
+ @Override
+ public void recycle() {
+
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ ReadOnlyUiObject that = (ReadOnlyUiObject) o;
+
+ return mNodeInfo.equals(that.mNodeInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + mNodeInfo.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return mNodeInfo.toString();
+ }
+}
diff --git a/app/src/main/java/com/stardust/autojs/codegeneration/UiSelectorGenerator.java b/app/src/main/java/com/stardust/autojs/codegeneration/UiSelectorGenerator.java
new file mode 100644
index 00000000..cd3444b0
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/codegeneration/UiSelectorGenerator.java
@@ -0,0 +1,133 @@
+package com.stardust.autojs.codegeneration;
+
+import com.stardust.automator.UiGlobalSelector;
+import com.stardust.automator.UiObject;
+import com.stardust.util.Consumer;
+
+import androidx.appcompat.widget.AppCompatEditText;
+
+import static com.stardust.autojs.codegeneration.CodeGenerator.FIND_ONE;
+import static com.stardust.autojs.codegeneration.CodeGenerator.UNTIL_FIND;
+import static com.stardust.autojs.codegeneration.CodeGenerator.WAIT_FOR;
+
+/**
+ * Created by Stardust on 2017/12/7.
+ */
+
+public class UiSelectorGenerator {
+
+ private UiObject mRoot;
+ private UiObject mTarget;
+ private boolean mUsingId = true;
+ private boolean mUsingDesc = true;
+ private boolean mUsingText = true;
+ private int mSearchMode = FIND_ONE;
+
+ public UiSelectorGenerator(UiObject root, UiObject target) {
+ mRoot = root;
+ mTarget = target;
+ }
+
+ public UiGlobalSelector generateSelector() {
+ UiGlobalSelector selector = new UiGlobalSelector();
+ if (mUsingId &&
+ tryWithStringCondition(selector, mTarget.id(), selector::id)) {
+ return selector;
+ }
+
+ if (tryWithStringCondition(selector, mTarget.className(), selector::className)) {
+ return selector;
+ }
+ if (mUsingText &&
+ tryWithStringCondition(selector, mTarget.text(), selector::text)) {
+ return selector;
+ }
+ if (mUsingDesc &&
+ tryWithStringCondition(selector, mTarget.desc(), selector::desc)) {
+ return selector;
+ }
+ if (mTarget.scrollable() && tryWithBooleanCondition(selector, mTarget.scrollable(), selector::scrollable)) {
+ return selector;
+ }
+ if (mTarget.clickable() && tryWithBooleanCondition(selector, mTarget.clickable(), selector::clickable)) {
+ return selector;
+ }
+ if (mTarget.selected() && tryWithBooleanCondition(selector, mTarget.selected(), selector::selected)) {
+ return selector;
+ }
+ if (mTarget.checkable() && tryWithBooleanCondition(selector, mTarget.checkable(), selector::checkable)) {
+ return selector;
+ }
+ if (mTarget.checked() && tryWithBooleanCondition(selector, mTarget.checked(), selector::checked)) {
+ return selector;
+ }
+ if (mTarget.longClickable() && tryWithBooleanCondition(selector, mTarget.longClickable(), selector::longClickable)) {
+ return selector;
+ }
+ if (tryWithIntCondition(selector, mTarget.depth(), selector::depth)) {
+ return selector;
+ }
+ return null;
+ }
+
+ public String generateSelectorCode() {
+ UiGlobalSelector selector = generateSelector();
+ if (selector == null) {
+ return null;
+ }
+ if (mSearchMode == FIND_ONE) {
+ return selector + ".findOne()";
+ } else if (mSearchMode == UNTIL_FIND) {
+ return selector + ".untilFind()";
+ } else {
+ return selector.toString();
+ }
+ }
+
+
+ public void setUsingId(boolean usingId) {
+ mUsingId = usingId;
+ }
+
+ public void setUsingDesc(boolean usingDesc) {
+ mUsingDesc = usingDesc;
+ }
+
+ public void setUsingText(boolean usingText) {
+ mUsingText = usingText;
+ }
+
+ public void setSearchMode(int searchMode) {
+ mSearchMode = searchMode;
+ }
+
+ private boolean tryWithBooleanCondition(UiGlobalSelector selector, boolean value, Consumer condition) {
+ condition.accept(value);
+ return shouldStopGeneration(selector);
+ }
+
+
+ private boolean tryWithStringCondition(UiGlobalSelector selector, String value, Consumer condition) {
+ if (value == null || value.isEmpty()) {
+ return false;
+ }
+ condition.accept(value);
+ return shouldStopGeneration(selector);
+ }
+
+ private boolean shouldStopGeneration(UiGlobalSelector selector) {
+ if (mSearchMode == UNTIL_FIND) {
+ return !selector.findAndReturnList(mRoot, 1).isEmpty();
+ } else {
+ return selector.findAndReturnList(mRoot, 2).size() == 1;
+
+ }
+ }
+
+ private boolean tryWithIntCondition(UiGlobalSelector selector, int value, Consumer condition) {
+ condition.accept(value);
+ return shouldStopGeneration(selector);
+ }
+
+
+}
diff --git a/app/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityBridge.java b/app/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityBridge.java
new file mode 100644
index 00000000..a42ee0a3
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityBridge.java
@@ -0,0 +1,152 @@
+package com.stardust.autojs.core.accessibility;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.stardust.app.AppOpsKt;
+import com.stardust.autojs.runtime.accessibility.AccessibilityConfig;
+import com.stardust.util.IntentUtil;
+import com.stardust.util.UiHandler;
+import com.stardust.autojs.core.activity.ActivityInfoProvider;
+import com.stardust.view.accessibility.AccessibilityNotificationObserver;
+import com.stardust.view.accessibility.AccessibilityService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Created by Stardust on 2017/4/2.
+ */
+
+public abstract class AccessibilityBridge {
+
+ public interface WindowFilter {
+ boolean filter(AccessibilityWindowInfo info);
+ }
+
+ public static final int MODE_NORMAL = 0;
+ public static final int MODE_FAST = 1;
+
+ public static final int FLAG_FIND_ON_UI_THREAD = 1;
+ public static final int FLAG_USE_USAGE_STATS = 2;
+ public static final int FLAG_USE_SHELL = 4;
+
+ private int mMode = MODE_NORMAL;
+ private int mFlags = 0;
+ private final AccessibilityConfig mConfig;
+ private WindowFilter mWindowFilter;
+ private final UiHandler mUiHandler;
+ private final Context mContext;
+
+ public AccessibilityBridge(Context context, AccessibilityConfig config, UiHandler uiHandler) {
+ mConfig = config;
+ mUiHandler = uiHandler;
+ mConfig.seal();
+ mContext = context;
+ }
+
+ public abstract void ensureServiceEnabled();
+
+ public abstract void waitForServiceEnabled();
+
+ public void post(Runnable r) {
+ mUiHandler.post(r);
+ }
+
+ @Nullable
+ public abstract AccessibilityService getService();
+
+ public List windowRoots() {
+ AccessibilityService service = getService();
+ if (service == null)
+ return Collections.emptyList();
+ ArrayList roots = new ArrayList<>();
+ if (mWindowFilter != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ for (AccessibilityWindowInfo window : service.getWindows()) {
+ if (mWindowFilter.filter(window)) {
+ AccessibilityNodeInfo root = window.getRoot();
+ if (root != null) {
+ roots.add(root);
+ }
+ }
+ }
+ return roots;
+ }
+ if ((mMode & MODE_FAST) != 0) {
+ return Collections.singletonList(service.fastRootInActiveWindow());
+ }
+ return Collections.singletonList(service.getRootInActiveWindow());
+ }
+
+ @Nullable
+ public AccessibilityNodeInfo getRootInCurrentWindow() {
+ AccessibilityService service = getService();
+ if (service == null)
+ return null;
+ if (mWindowFilter != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ for (AccessibilityWindowInfo window : service.getWindows()) {
+ if (mWindowFilter.filter(window)) {
+ return window.getRoot();
+ }
+ }
+ return null;
+ }
+ if ((mMode & MODE_FAST) != 0) {
+ return service.fastRootInActiveWindow();
+ }
+ return service.getRootInActiveWindow();
+ }
+
+ public AccessibilityNodeInfo getRootInActiveWindow() {
+ AccessibilityService service = getService();
+ if (service == null)
+ return null;
+ if ((mMode & MODE_FAST) != 0) {
+ return service.fastRootInActiveWindow();
+ }
+ return service.getRootInActiveWindow();
+ }
+
+ public void setWindowFilter(WindowFilter windowFilter) {
+ mWindowFilter = windowFilter;
+ }
+
+ public abstract ActivityInfoProvider getInfoProvider();
+
+ public void setMode(int mode) {
+ mMode = mode;
+ }
+
+ public int getFlags() {
+ return mFlags;
+ }
+
+ public void setFlags(int flags) {
+ mFlags = flags;
+ if ((mFlags & FLAG_USE_USAGE_STATS) != 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (!AppOpsKt.isOpPermissionGranted(mContext, AppOpsManager.OPSTR_GET_USAGE_STATS)) {
+ IntentUtil.requestAppUsagePermission(mContext);
+ throw new SecurityException("没有\"查看使用情况\"权限");
+ }
+ }
+ getInfoProvider().setUseUsageStats((mFlags & FLAG_USE_USAGE_STATS) != 0);
+ getInfoProvider().setUseShell((mFlags & FLAG_USE_SHELL) != 0);
+ }
+
+ @NonNull
+ public abstract AccessibilityNotificationObserver getNotificationObserver();
+
+ public AccessibilityConfig getConfig() {
+ return mConfig;
+ }
+}
diff --git a/app/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityServiceUsher.kt b/app/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityServiceUsher.kt
new file mode 100644
index 00000000..76a26c33
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityServiceUsher.kt
@@ -0,0 +1,27 @@
+package com.stardust.autojs.core.accessibility
+
+import android.accessibilityservice.AccessibilityServiceInfo
+import android.os.Build
+import com.stardust.autojs.core.pref.Pref
+import com.stardust.view.accessibility.AccessibilityService
+
+class AccessibilityServiceUsher : AccessibilityService() {
+
+ override fun onServiceConnected() {
+ val serviceInfo = serviceInfo
+ if (Pref.isStableModeEnabled) {
+ serviceInfo.flags = serviceInfo.flags and AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS.inv()
+ } else {
+ serviceInfo.flags = serviceInfo.flags or AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (Pref.isGestureObservingEnabled) {
+ serviceInfo.flags = serviceInfo.flags or AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ } else {
+ serviceInfo.flags = serviceInfo.flags and AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE.inv()
+ }
+ }
+ setServiceInfo(serviceInfo)
+ super.onServiceConnected()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stardust/autojs/core/accessibility/SimpleActionAutomator.kt b/app/src/main/java/com/stardust/autojs/core/accessibility/SimpleActionAutomator.kt
new file mode 100644
index 00000000..4912bfda
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/accessibility/SimpleActionAutomator.kt
@@ -0,0 +1,261 @@
+package com.stardust.autojs.core.accessibility
+
+import android.accessibilityservice.AccessibilityService
+import android.accessibilityservice.GestureDescription
+import android.graphics.Rect
+import android.os.Build
+import android.os.Handler
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.annotation.RequiresApi
+import com.stardust.autojs.annotation.ScriptInterface
+import com.stardust.autojs.runtime.ScriptRuntime
+import com.stardust.autojs.runtime.accessibility.AccessibilityConfig
+import com.stardust.automator.GlobalActionAutomator
+import com.stardust.automator.UiObject
+import com.stardust.automator.simple_action.ActionFactory
+import com.stardust.automator.simple_action.ActionTarget
+import com.stardust.automator.simple_action.SimpleAction
+import com.stardust.util.DeveloperUtils
+import com.stardust.util.ScreenMetrics
+
+/**
+ * Created by Stardust on 2017/4/2.
+ */
+
+class SimpleActionAutomator(private val mAccessibilityBridge: AccessibilityBridge, private val mScriptRuntime: ScriptRuntime) {
+
+ private lateinit var mGlobalActionAutomator: GlobalActionAutomator
+
+ private var mScreenMetrics: ScreenMetrics? = null
+
+ private val isRunningPackageSelf: Boolean
+ get() = DeveloperUtils.isSelfPackage(mAccessibilityBridge.infoProvider.latestPackage)
+
+ @ScriptInterface
+ fun text(text: String, i: Int): ActionTarget {
+ return ActionTarget.TextActionTarget(text, i)
+ }
+
+ @ScriptInterface
+ fun bounds(left: Int, top: Int, right: Int, bottom: Int): ActionTarget {
+ return ActionTarget.BoundsActionTarget(Rect(left, top, right, bottom))
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ @ScriptInterface
+ fun editable(i: Int): ActionTarget {
+ ScriptRuntime.requiresApi(Build.VERSION_CODES.LOLLIPOP)
+ return ActionTarget.EditableActionTarget(i)
+ }
+
+ @ScriptInterface
+ fun id(id: String): ActionTarget {
+ return ActionTarget.IdActionTarget(id)
+ }
+
+ @ScriptInterface
+ fun click(target: ActionTarget): Boolean {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_CLICK))
+ }
+
+ @ScriptInterface
+ fun longClick(target: ActionTarget): Boolean {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_LONG_CLICK))
+ }
+
+ @ScriptInterface
+ fun scrollUp(target: ActionTarget): Boolean {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD))
+ }
+
+ @ScriptInterface
+ fun scrollDown(target: ActionTarget): Boolean {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD))
+ }
+
+ @ScriptInterface
+ fun scrollBackward(i: Int): Boolean {
+ return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, i))
+ }
+
+ @ScriptInterface
+ fun scrollForward(i: Int): Boolean {
+ return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, i))
+ }
+
+ @ScriptInterface
+ fun scrollMaxBackward(): Boolean {
+ return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD))
+ }
+
+ @ScriptInterface
+ fun scrollMaxForward(): Boolean {
+ return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD))
+ }
+
+ @ScriptInterface
+ fun focus(target: ActionTarget): Boolean {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_FOCUS))
+ }
+
+ @ScriptInterface
+ fun select(target: ActionTarget): Boolean {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SELECT))
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ fun setText(target: ActionTarget, text: String): Boolean {
+ ScriptRuntime.requiresApi(Build.VERSION_CODES.LOLLIPOP)
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SET_TEXT, text))
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ fun appendText(target: ActionTarget, text: String): Boolean {
+ ScriptRuntime.requiresApi(Build.VERSION_CODES.LOLLIPOP)
+ return performAction(target.createAction(UiObject.ACTION_APPEND_TEXT, text))
+ }
+
+ @ScriptInterface
+ fun back(): Boolean {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
+ }
+
+ @ScriptInterface
+ fun home(): Boolean {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME)
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ fun powerDialog(): Boolean {
+ ScriptRuntime.requiresApi(Build.VERSION_CODES.LOLLIPOP)
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG)
+ }
+
+ @ScriptInterface
+ fun notifications(): Boolean {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)
+ }
+
+ @ScriptInterface
+ fun quickSettings(): Boolean {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS)
+ }
+
+ @ScriptInterface
+ fun recents(): Boolean {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun splitScreen(): Boolean {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN)
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun gesture(start: Long, duration: Long, vararg points: IntArray): Boolean {
+ prepareForGesture()
+ return mGlobalActionAutomator.gesture(start, duration, *points)
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun gestureAsync(start: Long, duration: Long, vararg points: IntArray) {
+ prepareForGesture()
+ mGlobalActionAutomator.gestureAsync(start, duration, *points)
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun gestures(strokes: Any): Boolean {
+ prepareForGesture()
+ @Suppress("UNCHECKED_CAST")
+ return mGlobalActionAutomator.gestures(*strokes as Array)
+ }
+
+ //如果这里用GestureDescription.StrokeDescription[]为参数,安卓7.0以下会因为找不到这个类而报错
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun gesturesAsync(strokes: Any) {
+ prepareForGesture()
+ @Suppress("UNCHECKED_CAST")
+ mGlobalActionAutomator.gesturesAsync(*strokes as Array)
+ }
+
+ private fun prepareForGesture() {
+ ScriptRuntime.requiresApi(24)
+ if (!::mGlobalActionAutomator.isInitialized) {
+ mGlobalActionAutomator = GlobalActionAutomator(Handler(mScriptRuntime.loopers.servantLooper)) {
+ ensureAccessibilityServiceEnabled()
+ return@GlobalActionAutomator mAccessibilityBridge.service!!
+ }
+ }
+ mGlobalActionAutomator.setScreenMetrics(mScreenMetrics)
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun click(x: Int, y: Int): Boolean {
+ prepareForGesture()
+ return mGlobalActionAutomator.click(x, y)
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun press(x: Int, y: Int, delay: Int): Boolean {
+ prepareForGesture()
+ return mGlobalActionAutomator.press(x, y, delay)
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun longClick(x: Int, y: Int): Boolean {
+ prepareForGesture()
+ return mGlobalActionAutomator.longClick(x, y)
+ }
+
+ @ScriptInterface
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, delay: Int): Boolean {
+ prepareForGesture()
+ return mGlobalActionAutomator.swipe(x1, y1, x2, y2, delay.toLong())
+ }
+
+ private fun performGlobalAction(action: Int): Boolean {
+ ensureAccessibilityServiceEnabled()
+ val service = mAccessibilityBridge.service ?: return false
+ return service.performGlobalAction(action)
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+ @ScriptInterface
+ fun paste(target: ActionTarget): Boolean {
+ ScriptRuntime.requiresApi(18)
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE))
+ }
+
+ private fun ensureAccessibilityServiceEnabled() {
+ mAccessibilityBridge.ensureServiceEnabled()
+ }
+
+ private fun performAction(simpleAction: SimpleAction): Boolean {
+ ensureAccessibilityServiceEnabled()
+ if (AccessibilityConfig.isUnintendedGuardEnabled() && isRunningPackageSelf) {
+ return false
+ }
+ val roots = mAccessibilityBridge.windowRoots().filter { it != null }
+ if (roots.isEmpty())
+ return false
+ var succeed = true
+ for (root in roots) {
+ succeed = succeed and simpleAction.perform(UiObject.createRoot(root))
+ }
+ return succeed
+ }
+
+ fun setScreenMetrics(metrics: ScreenMetrics) {
+ mScreenMetrics = metrics
+ }
+
+}
diff --git a/app/src/main/java/com/stardust/autojs/core/accessibility/UiSelector.kt b/app/src/main/java/com/stardust/autojs/core/accessibility/UiSelector.kt
new file mode 100644
index 00000000..254d5957
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/accessibility/UiSelector.kt
@@ -0,0 +1,372 @@
+package com.stardust.autojs.core.accessibility
+
+import android.os.Looper
+import android.os.SystemClock
+import android.util.Log
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.stardust.autojs.BuildConfig
+import com.stardust.autojs.annotation.ScriptInterface
+import com.stardust.autojs.runtime.exception.ScriptInterruptedException
+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.Filter
+import com.stardust.concurrent.VolatileBox
+import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator
+
+/**
+ * Created by Stardust on 2017/3/9.
+ */
+class UiSelector : UiGlobalSelector {
+ private val mAccessibilityBridge: AccessibilityBridge
+ private var mAllocator: AccessibilityNodeInfoAllocator? = null
+
+ constructor(accessibilityBridge: AccessibilityBridge) {
+ mAccessibilityBridge = accessibilityBridge
+ }
+
+ constructor(accessibilityBridge: AccessibilityBridge, allocator: AccessibilityNodeInfoAllocator?) {
+ mAccessibilityBridge = accessibilityBridge
+ mAllocator = allocator
+ }
+
+ protected fun find(max: Int): UiObjectCollection {
+ ensureAccessibilityServiceEnabled()
+ if ((mAccessibilityBridge.getFlags() and AccessibilityBridge.FLAG_FIND_ON_UI_THREAD) != 0
+ && Looper.myLooper() != Looper.getMainLooper()
+ ) {
+ val result = VolatileBox()
+ mAccessibilityBridge.post(Runnable { result.setAndNotify(findImpl(max)) })
+ return result.blockedGet()
+ }
+ return findImpl(max)
+ }
+
+ @ScriptInterface
+ fun find(): UiObjectCollection {
+ return find(Int.Companion.MAX_VALUE)
+ }
+
+ @ScriptInterface
+ protected fun findImpl(max: Int): UiObjectCollection {
+ val roots: MutableList = mAccessibilityBridge.windowRoots()
+ if (BuildConfig.DEBUG) Log.d(TAG, "find: roots = " + roots)
+ if (roots.isEmpty()) {
+ return UiObjectCollection.Companion.EMPTY
+ }
+ val result: MutableList = ArrayList()
+ for (root in roots) {
+ if (root == null) {
+ continue
+ }
+ if (root.getPackageName() != null && mAccessibilityBridge.getConfig().whiteListContains(root.getPackageName().toString())) {
+ Log.d(TAG, "package in white list, return null")
+ return UiObjectCollection.Companion.EMPTY
+ }
+ result.addAll(findAndReturnList(UiObject.Companion.createRoot(root, mAllocator), max - result.size))
+ if (result.size >= max) {
+ break
+ }
+ }
+ return UiObjectCollection.Companion.of(result)
+ }
+
+ public override fun textMatches(regex: String): UiGlobalSelector {
+ return super.textMatches(convertRegex(regex))
+ }
+
+ // TODO: 2018/1/30 更好的实现方式。
+ private fun convertRegex(regex: String): String {
+ if (regex.startsWith("/") && regex.endsWith("/") && regex.length > 2) {
+ return regex.substring(1, regex.length - 1)
+ }
+ return regex
+ }
+
+
+ public override fun classNameMatches(regex: String): UiGlobalSelector {
+ return super.classNameMatches(convertRegex(regex))
+ }
+
+ public override fun idMatches(regex: String): UiGlobalSelector {
+ return super.idMatches(convertRegex(regex))
+ }
+
+ public override fun packageNameMatches(regex: String): UiGlobalSelector {
+ return super.packageNameMatches(convertRegex(regex))
+ }
+
+ public override fun descMatches(regex: String): UiGlobalSelector {
+ return super.descMatches(convertRegex(regex))
+ }
+
+ private fun ensureAccessibilityServiceEnabled() {
+ mAccessibilityBridge.ensureServiceEnabled()
+ }
+
+ @ScriptInterface
+ fun untilFind(): UiObjectCollection {
+ ensureNonUiThread()
+ var uiObjectCollection = find()
+ while (uiObjectCollection.empty()) {
+ if (Thread.currentThread().isInterrupted()) {
+ throw ScriptInterruptedException()
+ }
+ try {
+ Thread.sleep(50)
+ } catch (e: InterruptedException) {
+ throw ScriptInterruptedException()
+ }
+ uiObjectCollection = find()
+ }
+ return uiObjectCollection
+ }
+
+ private fun ensureNonUiThread() {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // TODO: 2018/11/1 配置字符串
+ throw IllegalThreadStateException("不能在ui线程执行阻塞操作, 请在子线程或子脚本执行findOne()或untilFind()")
+ }
+ }
+
+ @ScriptInterface
+ fun findOne(timeout: Long): UiObject? {
+ var uiObjectCollection = find(1)
+ val start = SystemClock.uptimeMillis()
+ while (uiObjectCollection.empty()) {
+ if (Thread.currentThread().isInterrupted()) {
+ throw ScriptInterruptedException()
+ }
+ if (timeout > 0 && SystemClock.uptimeMillis() - start > timeout) {
+ return null
+ }
+ try {
+ Thread.sleep(50)
+ } catch (e: InterruptedException) {
+ throw ScriptInterruptedException()
+ }
+ uiObjectCollection = find(1)
+ }
+ return uiObjectCollection.get(0)
+ }
+
+ fun findOnce(): UiObject? {
+ return findOnce(0)
+ }
+
+ fun findOnce(index: Int): UiObject? {
+ val uiObjectCollection = find(index + 1)
+ if (index >= uiObjectCollection.size()) {
+ return null
+ }
+ return uiObjectCollection.get(index)
+ }
+
+ @ScriptInterface
+ fun findOne(): UiObject {
+ return untilFindOne()
+ }
+
+ @ScriptInterface
+ fun exists(): Boolean {
+ val collection = find()
+ return collection.nonEmpty()
+ }
+
+ fun untilFindOne(): UiObject {
+ return findOne(-1)!!
+ }
+
+ @ScriptInterface
+ fun waitFor() {
+ untilFind()
+ }
+
+ @ScriptInterface
+ public override fun id(id: String): UiSelector {
+ if (!id.contains(":")) {
+ addFilter(object : Filter {
+ override fun filter(node: UiObject): Boolean {
+ val fullId = mAccessibilityBridge.getInfoProvider().latestPackage + ":id/" + id
+ return fullId == node.getViewIdResourceName()
+ }
+
+ override fun toString(): String {
+ return "id(\"" + id + "\")"
+ }
+ })
+ } else {
+ super.id(id)
+ }
+ return this
+ }
+
+ public override fun idStartsWith(prefix: String): UiGlobalSelector {
+ if (!prefix.contains(":")) {
+ addFilter(object : Filter {
+ override fun filter(nodeInfo: UiObject): Boolean {
+ val fullIdPrefix = mAccessibilityBridge.getInfoProvider().latestPackage + ":id/" + prefix
+ val id = nodeInfo.getViewIdResourceName()
+ return id != null && id.startsWith(fullIdPrefix)
+ }
+
+ override fun toString(): String {
+ return "idStartsWith(\"" + prefix + "\")"
+ }
+ })
+ } else {
+ super.idStartsWith(prefix)
+ }
+ return this
+ }
+
+ private fun performAction(action: Int, vararg arguments: ActionArgument): Boolean {
+ return untilFind().performAction(action, *arguments)
+ }
+
+
+ @ScriptInterface
+ fun click(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_CLICK)
+ }
+
+ @ScriptInterface
+ fun longClick(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK)
+ }
+
+ @ScriptInterface
+ fun accessibilityFocus(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS)
+ }
+
+ @ScriptInterface
+ fun clearAccessibilityFocus(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS)
+ }
+
+ @ScriptInterface
+ fun focus(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_FOCUS)
+ }
+
+ @ScriptInterface
+ fun clearFocus(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS)
+ }
+
+ @ScriptInterface
+ fun copy(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_COPY)
+ }
+
+ @ScriptInterface
+ fun paste(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_PASTE)
+ }
+
+ @ScriptInterface
+ fun select(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_SELECT)
+ }
+
+ @ScriptInterface
+ fun cut(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_CUT)
+ }
+
+ @ScriptInterface
+ fun collapse(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_COLLAPSE)
+ }
+
+ @ScriptInterface
+ fun expand(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_EXPAND)
+ }
+
+ @ScriptInterface
+ fun dismiss(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_DISMISS)
+ }
+
+ @ScriptInterface
+ fun show(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SHOW_ON_SCREEN.getId())
+ }
+
+ @ScriptInterface
+ fun scrollForward(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD)
+ }
+
+ @ScriptInterface
+ fun scrollBackward(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD)
+ }
+
+ @ScriptInterface
+ fun scrollUp(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP.getId())
+ }
+
+ @ScriptInterface
+ fun scrollDown(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_DOWN.getId())
+ }
+
+ @ScriptInterface
+ fun scrollLeft(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_LEFT.getId())
+ }
+
+ @ScriptInterface
+ fun scrollRight(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT.getId())
+ }
+
+ @ScriptInterface
+ fun contextClick(): Boolean {
+ return performAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CONTEXT_CLICK.getId())
+ }
+
+ @ScriptInterface
+ fun setSelection(s: Int, e: Int): Boolean {
+ return performAction(
+ AccessibilityNodeInfoCompat.ACTION_SET_SELECTION,
+ ActionArgument.IntActionArgument(AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SELECTION_START_INT, s),
+ ActionArgument.IntActionArgument(AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SELECTION_END_INT, e)
+ )
+ }
+
+ @ScriptInterface
+ fun setText(text: String): Boolean {
+ return performAction(
+ AccessibilityNodeInfoCompat.ACTION_SET_TEXT,
+ ActionArgument.CharSequenceActionArgument(AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text)
+ )
+ }
+
+ @ScriptInterface
+ fun setProgress(value: Float): Boolean {
+ return performAction(
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_PROGRESS.getId(),
+ ActionArgument.FloatActionArgument(AccessibilityNodeInfoCompat.ACTION_ARGUMENT_PROGRESS_VALUE, value)
+ )
+ }
+
+ @ScriptInterface
+ fun scrollTo(row: Int, column: Int): Boolean {
+ return performAction(
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_TO_POSITION.getId(),
+ ActionArgument.IntActionArgument(AccessibilityNodeInfoCompat.ACTION_ARGUMENT_ROW_INT, row),
+ ActionArgument.IntActionArgument(AccessibilityNodeInfoCompat.ACTION_ARGUMENT_COLUMN_INT, column)
+ )
+ }
+
+ companion object {
+ private const val TAG = "UiSelector"
+ }
+}
diff --git a/app/src/main/java/com/stardust/autojs/core/activity/ActivityInfoProvider.kt b/app/src/main/java/com/stardust/autojs/core/activity/ActivityInfoProvider.kt
new file mode 100644
index 00000000..404684ec
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/activity/ActivityInfoProvider.kt
@@ -0,0 +1,199 @@
+@file:Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "SameParameterValue")
+
+package com.stardust.autojs.core.activity
+
+import android.accessibilityservice.AccessibilityService
+import android.app.AppOpsManager
+import android.app.usage.UsageStatsManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.util.Log
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityWindowInfo
+import com.stardust.app.isOpPermissionGranted
+import com.stardust.autojs.core.util.Shell
+import com.stardust.view.accessibility.AccessibilityDelegate
+import java.util.regex.Pattern
+
+/**
+ * Created by Stardust on 2017/3/9.
+ */
+
+class ActivityInfoProvider(private val context: Context) : AccessibilityDelegate {
+
+ private val mPackageManager: PackageManager = context.packageManager
+
+ @Volatile
+ private var mLatestPackage: String = ""
+ @Volatile
+ private var mLatestActivity: String = ""
+ private var mLatestComponentFromShell: ComponentName? = null
+
+ private var mShell: Shell? = null
+ private var mUseShell = false
+
+ val latestPackage: String
+ get() {
+ val compFromShell = mLatestComponentFromShell
+ if (useShell && compFromShell != null) {
+ return compFromShell.packageName
+ }
+ if (useUsageStats) {
+ mLatestPackage = getLatestPackageByUsageStats()
+ }
+ return mLatestPackage
+ }
+
+ val latestActivity: String
+ get() {
+ val compFromShell = mLatestComponentFromShell
+ if (useShell && compFromShell != null) {
+ return compFromShell.className
+ }
+ return mLatestActivity
+ }
+
+ var useUsageStats: Boolean = false
+
+ var useShell: Boolean
+ get() = mUseShell
+ set(value) {
+ if (value) {
+ mShell.let {
+ if (it == null) {
+ mShell = createShell(200)
+ }
+ }
+ } else {
+ mShell?.exit()
+ mShell = null
+ }
+ mUseShell = value
+ }
+
+ override val eventTypes: Set?
+ get() = AccessibilityDelegate.ALL_EVENT_TYPES
+
+ override fun onAccessibilityEvent(service: AccessibilityService, event: AccessibilityEvent): Boolean {
+ if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ val window = service.getWindow(event.windowId)
+ if (window?.isFocused != false) {
+ setLatestComponent(event.packageName, event.className)
+ return false
+ }
+ }
+ return false
+ }
+
+ fun getLatestPackageByUsageStatsIfGranted(): String {
+ if (context.isOpPermissionGranted(AppOpsManager.OPSTR_GET_USAGE_STATS)) {
+ return getLatestPackageByUsageStats()
+ }
+ return mLatestPackage
+ }
+
+ private fun setLatestComponentFromShellOutput(output: String) {
+ val matcher = WINDOW_PATTERN.matcher(output)
+ if (!matcher.find() || matcher.groupCount() < 1) {
+ Log.w(LOG_TAG, "invalid format: $output")
+ return
+ }
+ val latestPackage = matcher.group(1)
+ if (latestPackage.contains(":")) {
+ return
+ }
+ val latestActivity = if (matcher.groupCount() >= 2) {
+ matcher.group(2).orEmpty()
+ } else {
+ ""
+ }
+ Log.d(LOG_TAG, "setLatestComponent: output = $output, comp = $latestPackage/$latestActivity")
+ mLatestComponentFromShell = ComponentName(latestPackage, latestActivity)
+ }
+
+ private fun createShell(dumpInterval: Int): Shell {
+ val shell = Shell(true)
+ shell.setCallback(object : Shell.Callback {
+ override fun onOutput(str: String) {
+
+ }
+
+ override fun onNewLine(line: String) {
+ setLatestComponentFromShellOutput(line)
+ }
+
+ override fun onInitialized() {
+ }
+
+ override fun onInterrupted(e: InterruptedException) {
+
+ }
+ })
+ shell.exec(DUMP_WINDOW_COMMAND.format(dumpInterval))
+ return shell
+ }
+
+ fun getLatestPackageByUsageStats(): String {
+ val usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
+ val current = System.currentTimeMillis()
+ val usageStats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, current - 60 * 60 * 1000, current)
+ return if (usageStats.isEmpty()) {
+ mLatestPackage
+ } else {
+ usageStats.sortBy {
+ it.lastTimeStamp
+ }
+ usageStats.last().packageName
+ }
+
+ }
+
+ private fun setLatestComponent(latestPackage: CharSequence?, latestClass: CharSequence?) {
+ if (latestPackage == null)
+ return
+ val latestPackageStr = latestPackage.toString()
+ val latestClassStr = (latestClass ?: "").toString()
+ if (isPackageExists(latestPackageStr)) {
+ mLatestPackage = latestPackage.toString()
+ mLatestActivity = latestClassStr
+ }
+ Log.d(LOG_TAG, "setLatestComponent: $latestPackage/$latestClassStr $mLatestPackage/$mLatestActivity")
+ }
+
+ private fun isPackageExists(packageName: String): Boolean {
+ return try {
+ mPackageManager.getPackageInfo(packageName, 0)
+ true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
+ companion object {
+ private val WINDOW_PATTERN = Pattern.compile("Window\\{\\S+\\s\\S+\\s([^\\/]+)\\/?([^}]+)?\\}")
+ private val DUMP_WINDOW_COMMAND = """
+ oldActivity=""
+ currentActivity=`dumpsys window windows | grep -E 'mCurrentFocus'`
+ while true
+ do
+ if [[ ${'$'}oldActivity != ${'$'}currentActivity && ${'$'}currentActivity != *"=null"* ]]; then
+ echo ${'$'}currentActivity
+ oldActivity=${'$'}currentActivity
+ fi
+ currentActivity=`dumpsys window windows | grep -E 'mCurrentFocus'`
+ done
+ """.trimIndent()
+
+ private const val LOG_TAG = "ActivityInfoProvider"
+ }
+}
+
+private fun AccessibilityService.getWindow(windowId: Int): AccessibilityWindowInfo? {
+ windows.forEach {
+ if (it.id == windowId) {
+ return it
+ }
+ }
+ return null
+}
diff --git a/app/src/main/java/com/stardust/autojs/core/boardcast/Broadcast.java b/app/src/main/java/com/stardust/autojs/core/boardcast/Broadcast.java
new file mode 100644
index 00000000..ddc56110
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/boardcast/Broadcast.java
@@ -0,0 +1,27 @@
+package com.stardust.autojs.core.boardcast;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Created by Stardust on 2018/4/1.
+ */
+
+public class Broadcast {
+
+ private static CopyOnWriteArrayList sEventEmitters = new CopyOnWriteArrayList<>();
+
+ public static void registerListener(BroadcastEmitter eventEmitter) {
+ sEventEmitters.add(eventEmitter);
+ }
+
+ public static boolean unregisterListener(BroadcastEmitter eventEmitter) {
+ return sEventEmitters.remove(eventEmitter);
+ }
+
+ public static void send(String eventName, Object[] args) {
+ for (BroadcastEmitter emitter : sEventEmitters) {
+ emitter.onBroadcast(eventName, args);
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/stardust/autojs/core/boardcast/BroadcastEmitter.java b/app/src/main/java/com/stardust/autojs/core/boardcast/BroadcastEmitter.java
new file mode 100644
index 00000000..a5e77f9d
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/boardcast/BroadcastEmitter.java
@@ -0,0 +1,31 @@
+package com.stardust.autojs.core.boardcast;
+
+import com.stardust.autojs.core.eventloop.EventEmitter;
+import com.stardust.autojs.core.looper.Timer;
+import com.stardust.autojs.runtime.ScriptBridges;
+
+/**
+ * Created by Stardust on 2018/4/1.
+ */
+
+public class BroadcastEmitter extends EventEmitter {
+
+ public BroadcastEmitter(ScriptBridges bridges, Timer timer) {
+ super(bridges, timer);
+ Broadcast.registerListener(this);
+ }
+
+ public boolean onBroadcast(String eventName, Object... args) {
+ return super.emit(eventName, args);
+ }
+
+ public void unregister() {
+ Broadcast.unregisterListener(this);
+ }
+
+ @Override
+ public boolean emit(String eventName, Object... args) {
+ Broadcast.send(eventName, args);
+ return true;
+ }
+}
diff --git a/app/src/main/java/com/stardust/autojs/core/console/ConsoleFloaty.java b/app/src/main/java/com/stardust/autojs/core/console/ConsoleFloaty.java
new file mode 100644
index 00000000..8c9b9669
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/console/ConsoleFloaty.java
@@ -0,0 +1,133 @@
+package com.stardust.autojs.core.console;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import androidx.annotation.Nullable;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import org.autojs.autojs.ui.enhancedfloaty.FloatyService;
+import org.autojs.autojs.ui.enhancedfloaty.ResizableExpandableFloaty;
+import org.autojs.autojs.ui.enhancedfloaty.ResizableExpandableFloatyWindow;
+import com.stardust.util.ScreenMetrics;
+import com.stardust.util.ViewUtil;
+import org.autojs.autojs6.R;
+
+/**
+ * Created by Stardust on 2017/4/20.
+ */
+
+public class ConsoleFloaty extends ResizableExpandableFloaty.AbstractResizableExpandableFloaty {
+
+ private ContextWrapper mContextWrapper;
+ private View mResizer, mMoveCursor;
+ private TextView mTitleView;
+ private ConsoleImpl mConsole;
+ private CharSequence mTitle;
+ private View mExpandedView;
+
+ public ConsoleFloaty(ConsoleImpl console) {
+ mConsole = console;
+ setShouldRequestFocusWhenExpand(false);
+ setInitialX(100);
+ setInitialY(1000);
+ setCollapsedViewUnpressedAlpha(1.0f);
+ }
+
+ @Override
+ public int getInitialWidth() {
+ return WindowManager.LayoutParams.WRAP_CONTENT; //ScreenMetrics.getDeviceScreenWidth() * 2 / 3;
+ }
+
+ @Override
+ public int getInitialHeight() {
+ return WindowManager.LayoutParams.WRAP_CONTENT;//ScreenMetrics.getDeviceScreenHeight() / 3;
+ }
+
+ @Override
+ public View inflateCollapsedView(FloatyService service, final ResizableExpandableFloatyWindow window) {
+ ensureContextWrapper(service);
+ return View.inflate(mContextWrapper, R.layout.floating_window_collapse, null);
+ }
+
+ private void ensureContextWrapper(Context context) {
+ if (mContextWrapper == null) {
+ mContextWrapper = new ContextThemeWrapper(context, R.style.ConsoleTheme);
+ }
+ }
+
+ @Override
+ public View inflateExpandedView(FloatyService service, ResizableExpandableFloatyWindow window) {
+ ensureContextWrapper(service);
+ View view = View.inflate(mContextWrapper, R.layout.floating_console_expand, null);
+ setListeners(view, window);
+ setUpConsole(view, window);
+ setInitialMeasure(view);
+ mExpandedView = view;
+ return view;
+ }
+
+ public View getExpandedView() {
+ return mExpandedView;
+ }
+
+ private void setInitialMeasure(final View view) {
+ view.post(() -> ViewUtil.setViewMeasure(view, ScreenMetrics.getDeviceScreenWidth() * 2 / 3,
+ ScreenMetrics.getDeviceScreenHeight() / 3));
+ }
+
+ private void initConsoleTitle(View view) {
+ mTitleView = view.findViewById(R.id.title);
+ if (mTitle != null) {
+ mTitleView.setText(mTitle);
+ }
+ }
+
+ private void setListeners(final View view, final ResizableExpandableFloatyWindow window) {
+ setWindowOperationIconListeners(view, window);
+ }
+
+ private void setUpConsole(View view, ResizableExpandableFloatyWindow window) {
+ ConsoleView consoleView = view.findViewById(R.id.console);
+ consoleView.setConsole(mConsole);
+ consoleView.setWindow(window);
+ initConsoleTitle(view);
+ }
+
+ private void setWindowOperationIconListeners(View view, final ResizableExpandableFloatyWindow window) {
+ view.findViewById(R.id.close).setOnClickListener(v -> window.close());
+ view.findViewById(R.id.move_or_resize).setOnClickListener(v -> {
+ if (mMoveCursor.getVisibility() == View.VISIBLE) {
+ mMoveCursor.setVisibility(View.GONE);
+ mResizer.setVisibility(View.GONE);
+ } else {
+ mMoveCursor.setVisibility(View.VISIBLE);
+ mResizer.setVisibility(View.VISIBLE);
+ }
+ });
+ view.findViewById(R.id.minimize).setOnClickListener(v -> window.collapse());
+ }
+
+ @Nullable
+ @Override
+ public View getResizerView(View expandedView) {
+ mResizer = expandedView.findViewById(R.id.resizer);
+ return mResizer;
+ }
+
+ @Nullable
+ @Override
+ public View getMoveCursorView(View expandedView) {
+ mMoveCursor = expandedView.findViewById(R.id.move_cursor);
+ return mMoveCursor;
+ }
+
+ public void setTitle(final CharSequence title) {
+ mTitle = title;
+ if (mTitleView != null) {
+ mTitleView.post(() -> mTitleView.setText(title));
+ }
+ }
+}
diff --git a/app/src/main/java/com/stardust/autojs/core/console/ConsoleImpl.java b/app/src/main/java/com/stardust/autojs/core/console/ConsoleImpl.java
new file mode 100644
index 00000000..7cdc0025
--- /dev/null
+++ b/app/src/main/java/com/stardust/autojs/core/console/ConsoleImpl.java
@@ -0,0 +1,298 @@
+package com.stardust.autojs.core.console;
+
+import android.content.Context;
+import android.content.Intent;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.view.WindowManager;
+
+import org.autojs.autojs6.R;
+import com.stardust.autojs.annotation.ScriptInterface;
+import com.stardust.autojs.runtime.ScriptRuntime;
+import com.stardust.autojs.runtime.api.AbstractConsole;
+import com.stardust.autojs.runtime.api.Console;
+import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
+import com.stardust.autojs.util.FloatingPermission;
+import org.autojs.autojs.ui.enhancedfloaty.FloatyService;
+import org.autojs.autojs.ui.enhancedfloaty.ResizableExpandableFloatyWindow;
+import com.stardust.util.UiHandler;
+import com.stardust.util.ViewUtil;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Created by Stardust on 2017/5/2.
+ */
+
+public class ConsoleImpl extends AbstractConsole {
+
+ public static class LogEntry implements Comparable {
+
+ public int id;
+ public int level;
+ public CharSequence content;
+ public boolean newLine = false;
+
+ public LogEntry(int id, int level, CharSequence content) {
+ this.id = id;
+ this.level = level;
+ this.content = content;
+ }
+
+ public LogEntry(int id, int level, CharSequence content, boolean newLine) {
+ this.id = id;
+ this.level = level;
+ this.content = content;
+ this.newLine = newLine;
+ }
+
+ @Override
+ public int compareTo(@NonNull LogEntry o) {
+ return 0;
+ }
+ }
+
+ public interface LogListener {
+ void onNewLog(LogEntry logEntry);
+
+ void onLogClear();
+ }
+
+ private final Object WINDOW_SHOW_LOCK = new Object();
+ private final Console mGlobalConsole;
+ private final ArrayList mLogEntries = new ArrayList<>();
+ private AtomicInteger mIdCounter = new AtomicInteger(0);
+ private ResizableExpandableFloatyWindow mFloatyWindow;
+ private ConsoleFloaty mConsoleFloaty;
+ private WeakReference mLogListener;
+ private UiHandler mUiHandler;
+ private BlockingQueue mInput = new ArrayBlockingQueue<>(1);
+ private WeakReference mConsoleView;
+ private volatile boolean mShown = false;
+ private int mX, mY;
+
+ public ConsoleImpl(UiHandler uiHandler) {
+ this(uiHandler, null);
+ }
+
+ public ConsoleImpl(UiHandler uiHandler, Console globalConsole) {
+ mUiHandler = uiHandler;
+ mConsoleFloaty = new ConsoleFloaty(this);
+ mGlobalConsole = globalConsole;
+ mFloatyWindow = new ResizableExpandableFloatyWindow(mConsoleFloaty) {
+ @Override
+ public void onCreate(FloatyService service, WindowManager manager) {
+ super.onCreate(service, manager);
+ expand();
+ mFloatyWindow.getWindowBridge().updatePosition(mX, mY);
+ synchronized (WINDOW_SHOW_LOCK) {
+ mShown = true;
+ WINDOW_SHOW_LOCK.notifyAll();
+ }
+ }
+ };
+ }
+
+ public void setConsoleView(ConsoleView consoleView) {
+ mConsoleView = new WeakReference<>(consoleView);
+ setLogListener(consoleView);
+ synchronized (this) {
+ this.notify();
+ }
+ }
+
+
+ public void setLogListener(LogListener logListener) {
+ mLogListener = new WeakReference<>(logListener);
+ }
+
+ public ArrayList getAllLogs() {
+ return mLogEntries;
+ }
+
+ public void printAllStackTrace(Throwable t) {
+ println(android.util.Log.ERROR, ScriptRuntime.getStackTrace(t, true));
+ }
+
+ public String getStackTrace(Throwable t) {
+ return ScriptRuntime.getStackTrace(t, false);
+ }
+
+ @Override
+ public String println(int level, CharSequence charSequence) {
+ LogEntry logEntry = new LogEntry(mIdCounter.getAndIncrement(), level, charSequence, true);
+ synchronized (mLogEntries) {
+ mLogEntries.add(logEntry);
+ }
+ if (mGlobalConsole != null) {
+ mGlobalConsole.println(level, charSequence);
+ }
+ if (mLogListener != null && mLogListener.get() != null) {
+ mLogListener.get().onNewLog(logEntry);
+ }
+ return null;
+ }
+
+
+ @Override
+ public void write(int level, CharSequence charSequence) {
+ println(level, charSequence);
+ }
+
+
+ @Override
+ public void clear() {
+ synchronized (mLogEntries) {
+ mLogEntries.clear();
+ }
+ if (mLogListener != null && mLogListener.get() != null) {
+ mLogListener.get().onLogClear();
+ }
+ }
+
+ @Override
+ public void show() {
+ if (mShown) {
+ return;
+ }
+ if (!FloatingPermission.canDrawOverlays(mUiHandler.getContext())) {
+ FloatingPermission.manageDrawOverlays(mUiHandler.getContext());
+ mUiHandler.toast(R.string.error_no_display_over_other_apps_permission);
+ return;
+ }
+ startFloatyService();
+ mUiHandler.post(() -> {
+ try {
+ FloatyService.addWindow(mFloatyWindow);
+ // SecurityException: https://github.com/hyb1996-guest/AutoJsIssueReport/issues/4781
+ } catch (WindowManager.BadTokenException | SecurityException e) {
+ e.printStackTrace();
+ mUiHandler.toast(R.string.error_no_display_over_other_apps_permission);
+ }
+ });
+ synchronized (WINDOW_SHOW_LOCK) {
+ if (mShown) {
+ return;
+ }
+ try {
+ WINDOW_SHOW_LOCK.wait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private void startFloatyService() {
+ Context context = mUiHandler.getContext();
+ context.startService(new Intent(context, FloatyService.class));
+ }
+
+ @Override
+ public void hide() {
+ mUiHandler.post(() -> {
+ synchronized (WINDOW_SHOW_LOCK) {
+ if (!mShown)
+ return;
+ try {
+ mFloatyWindow.close();
+ } catch (IllegalArgumentException ignored) {
+
+ }
+ mShown = false;
+ }
+ });
+ }
+
+
+ public void setSize(int w, int h) {
+ if (mShown) {
+ mUiHandler.post(() -> {
+ if (mShown) {
+ ViewUtil.setViewMeasure(mConsoleFloaty.getExpandedView(), w, h);
+ }
+ });
+ }
+ }
+
+ public void setPosition(int x, int y) {
+ mX = x;
+ mY = y;
+ if (mShown) {
+ mUiHandler.post(() -> {
+ if (mShown)
+ mFloatyWindow.getWindowBridge().updatePosition(x, y);
+ });
+ }
+ }
+
+ @ScriptInterface
+ public String rawInput() {
+ if (mConsoleView == null || mConsoleView.get() == null) {
+ if (!mShown) {
+ show();
+ }
+ waitForConsoleView();
+ }
+ mConsoleView.get().showEditText();
+ try {
+ return mInput.take();
+ } catch (InterruptedException e) {
+ throw new ScriptInterruptedException();
+ }
+ }
+
+ private void waitForConsoleView() {
+ synchronized (this) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ throw new ScriptInterruptedException();
+ }
+ }
+ }
+
+ @ScriptInterface
+ public String rawInput(Object data, Object... param) {
+ log(data, param);
+ return rawInput();
+ }
+
+ boolean submitInput(@NonNull CharSequence input) {
+ return mInput.offer(input.toString());
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mConsoleFloaty.setTitle(title);
+ }
+
+ @Override
+ public void error(@Nullable Object data, Object... options) {
+ if (data instanceof Throwable) {
+ data = getStackTrace((Throwable) data);
+ }
+ if (options != null && options.length > 0) {
+ StringBuilder sb = new StringBuilder(data == null ? "" : data.toString());
+ ArrayList