4 Commits

72 changed files with 2624 additions and 2067 deletions

5
.gitignore vendored
View File

@@ -2,6 +2,7 @@
*.exe
*.iml
*.jks
*.log
.DS_Store
.externalNativeBuild
@@ -18,4 +19,6 @@ output-metadata.json
/tools/
/local.properties
/sign.properties
/sign.properties
**/assets/declarations/

View File

@@ -53,6 +53,7 @@
* 支持将脚本文件或项目打包为 APK 文件
* 支持利用 Root 权限扩展功能 (屏幕点击/滑动/录制/Shell)
* 支持作为 Tasker 插件使用
* 支持与 VSCode 连接并进行桌面开发 (需要 [AutoJs6-VSCode-Ext](https://github.com/SuperMonster003/AutoJs6-VSCode-Ext) 插件)
******
@@ -98,6 +99,24 @@
[comment]: <> "Version history only shows last 3 versions"
# v6.0.1
###### 2022/01/01
* `新增` 连接 VSCode 插件支持客户端 (LAN) 及服务端 (LAN/ADB) 方式 (Ref to Auto.js Pro)
* `新增` 增加 $base64 全局对象 (Ref to Auto.js Pro)
* `新增` 增加 isInteger/isNullish/isPlainObject/isPrimitive/isReference 全局方法
* `新增` 增加 polyfill (Object.getOwnPropertyDescriptors)
* `新增` 增加 polyfill (Array.prototype.flat)
* `优化` 扩展 global.sleep 支持 随机范围/负数兼容
* `优化` 扩展 global.toast 支持 时长控制/强制覆盖控制/dismiss
* `优化` 包名对象全局化 (okhttp3/androidx/de)
* `优化` 升级 Android Material 版本 1.5.0-beta01 -> 1.6.0-alpha01
* `优化` 升级 Android Gradle 插件版本 7.2.0-alpha04 -> 7.2.0-alpha06
* `优化` 升级 Kotlinx Coroutines 版本 1.5.2-native-mt -> 1.6.0-native-mt
* `优化` 升级 Kotlin Gradle 插件版本 1.6.0 -> 1.6.10
* `优化` 升级 Gradle 发行版本 7.3 -> 7.3.3
# v6.0.0
###### 2021/12/01
@@ -171,7 +190,11 @@
******
* [Auto.js](https://github.com/hyb1996/Auto.js-VSCode-Extension) { author: [hyb1996](https://github.com/hyb1996) }
- `适用于 VS Code 的桌面开发插件`
* [Auto.js-TypeScript-Declarations](https://github.com/SuperMonster003/Auto.js-TypeScript-Declarations) { author: [SuperMonster003](https://github.com/SuperMonster003) }
- `Auto.js 声明文件 (.d.ts)`
* [AutoJs6-VSCode-Ext](https://github.com/SuperMonster003/AutoJs6-VSCode-Ext) { author: [SuperMonster003](https://github.com/SuperMonster003) }
- `适用于 VSCode 的桌面开发插件 (二次开发项目)`
* [AutoX](https://github.com/kkevsekk1/AutoX) { author: [kkevsekk1](https://github.com/kkevsekk1) }
- `安卓平台 JavaScript 自动化工具 (二次开发项目)`
[//]: # (* [Auto.js-TypeScript-Declarations]&#40;https://github.com/SuperMonster003/Auto.js-TypeScript-Declarations&#41; { author: [SuperMonster003]&#40;https://github.com/SuperMonster003&#41; })
[//]: # ( - `Auto.js 声明文件 &#40;.d.ts&#41;`)

View File

@@ -5,9 +5,9 @@ plugins {
id "kotlin-kapt"
}
def AnnotationsVer = "4.8.0"
def SupportLibsVer = "28.0.0"
def ButterKnifeVer = "10.2.3"
def annotationsVer = "4.8.0"
def butterKnifeVer = "10.2.3"
def leakcanaryVer = "2.7"
def properties = new Properties()
def propFile = new File("sign.properties")
@@ -15,21 +15,15 @@ def isPropFileExists = propFile.exists()
isPropFileExists && properties.load(new FileInputStream(propFile))
configurations.all {
resolutionStrategy {
force "com.android.support:support-v4:${SupportLibsVer}"
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_16
}
}
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:deprecation' << '-Xlint:unchecked'
}
// tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked"
// }
android {
signingConfigs {
@@ -52,7 +46,7 @@ android {
javaCompileOptions {
annotationProcessorOptions {
arguments = [
'resourcePackageName': applicationId,
"resourcePackageName": applicationId,
"androidManifestFile": "$projectDir/src/main/AndroidManifest.xml".toString()
]
}
@@ -66,6 +60,10 @@ android {
if (isPropFileExists) {
signingConfig signingConfigs.release
}
// Disable Crashlytics reports.
ext.enableCrashlytics = false
// Disables PNG crunching for the release build type.
crunchPngs false
}
release {
shrinkResources false
@@ -115,6 +113,13 @@ android {
buildConfigField "String", "APP_SINCE_DATE", "\"${versions.appSinceDate}\""
}
}
applicationVariants.all { variant ->
variant.mergeAssetsProvider.configure {
doLast {
delete(fileTree(dir: outputDir, includes: ["declarations/**"]))
}
}
}
}
repositories {
@@ -122,23 +127,27 @@ repositories {
}
dependencies {
testImplementation "junit:junit:4.13.2"
// JUnit
testImplementation "junit:junit:${junitVer}"
// LeakCanary
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakcanaryVer}"
// Kotlin
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2-native-mt"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0-native-mt"
// Android Annotations
kapt "org.androidannotations:androidannotations:${AnnotationsVer}"
implementation "org.androidannotations:androidannotations-api:${AnnotationsVer}"
kapt "org.androidannotations:androidannotations:${annotationsVer}"
implementation "org.androidannotations:androidannotations-api:${annotationsVer}"
// ButterKnife
kapt "com.jakewharton:butterknife-compiler:${ButterKnifeVer}"
implementation "com.jakewharton:butterknife:${ButterKnifeVer}"
kapt "com.jakewharton:butterknife-compiler:${butterKnifeVer}"
implementation "com.jakewharton:butterknife:${butterKnifeVer}"
// Android supports
implementation "androidx.appcompat:appcompat:1.4.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "com.google.android.material:material:1.5.0-beta01"
implementation "com.google.android.material:material:1.6.0-alpha01"
implementation "androidx.multidex:multidex:2.0.1"
// Material Dialogs
@@ -205,13 +214,10 @@ dependencies {
// Android Job
implementation "com.evernote:android-job:1.4.2"
// Leakcanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
// Extracted from com.github.hyb1996:MutableTheme:1.0.0
api 'com.jrummyapps:colorpicker:2.1.7'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.github.ozodrukh:CircularReveal:2.0.1'
implementation "com.jrummyapps:colorpicker:2.1.7"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "com.github.ozodrukh:CircularReveal:2.0.1"
// Bugly
implementation project(":libs:com.tencent.bugly.crashreport-2.6.6")

View File

@@ -4,6 +4,24 @@
******
# v6.0.1
###### 2022/01/01
* `新增` 连接 VSCode 插件支持客户端 (LAN) 及服务端 (LAN/ADB) 方式 (Ref to Auto.js Pro)
* `新增` 增加 $base64 "工具类" (Ref to Auto.js Pro)
* `新增` 增加 isInteger/isNullish/isPlainObject/isPrimitive/isReference 全局方法
* `新增` 增加 polyfill (Object.getOwnPropertyDescriptors)
* `新增` 增加 polyfill (Array.prototype.flat)
* `优化` 扩展 global.sleep 支持 随机范围/负数兼容
* `优化` 扩展 global.toast 支持 时长控制/强制覆盖控制/dismiss
* `优化` 包名对象全局化 (okhttp3/androidx/de)
* `优化` 升级 Android Material 版本 1.5.0-beta01 -> 1.6.0-alpha01
* `优化` 升级 Android Gradle 插件版本 7.2.0-alpha04 -> 7.2.0-alpha06
* `优化` 升级 Kotlinx Coroutines 版本 1.5.2-native-mt -> 1.6.0-native-mt
* `优化` 升级 Kotlin Gradle 插件版本 1.6.0 -> 1.6.10
* `优化` 升级 Gradle 发行版本 7.3 -> 7.3.3
# v6.0.0
###### 2021/12/01

View File

@@ -88,11 +88,9 @@ public class ThemeColorHelper {
@TargetApi(21)
public static void setStatusBarColor(Activity activity, int color) {
if (Build.VERSION.SDK_INT >= 21) {
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(color);
}
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(color);
}
public static void setBackgroundColor(View view, int color) {

View File

@@ -159,11 +159,9 @@ public class ThemeColorManager {
@TargetApi(21)
public static void add(Activity activity) {
if (Build.VERSION.SDK_INT >= 21) {
activities.add(new WeakReference<>(activity));
if (shouldApplyThemeColor())
activity.getWindow().setStatusBarColor(themeColor.colorPrimary);
}
activities.add(new WeakReference<>(activity));
if (shouldApplyThemeColor())
activity.getWindow().setStatusBarColor(themeColor.colorPrimary);
}
@TargetApi(21)

View File

@@ -1,6 +1,7 @@
package com.stardust.theme.app;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -203,6 +204,7 @@ public class ColorSelectActivity extends AppCompatActivity {
return mColors.get(mSelectedPosition).themeColor;
}
@SuppressLint("NotifyDataSetChanged")
public void setSelectedPosition(int selectedPosition) {
if (mSelectedPosition != SELECT_NONE) {
int oldSelectedPosition = mSelectedPosition;
@@ -211,7 +213,7 @@ public class ColorSelectActivity extends AppCompatActivity {
getAdapter().notifyItemChanged(mSelectedPosition);
} else {
this.mSelectedPosition = selectedPosition;
getAdapter().notifyDataSetChanged();
Objects.requireNonNull(getAdapter()).notifyDataSetChanged();
}
}

View File

@@ -6,6 +6,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Looper;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.stardust.app.GlobalAppContext;
@@ -15,12 +16,15 @@ import com.stardust.autojs.runtime.accessibility.AccessibilityConfig;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.autojs.runtime.exception.ScriptException;
import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.view.accessibility.LayoutInspector;
import com.stardust.view.accessibility.NodeInfo;
import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.Pref;
import org.autojs.autojs.R;
import org.autojs.autojs.external.fileprovider.AppFileProvider;
import org.autojs.autojs.pluginclient.DevPluginService;
import org.autojs.autojs.tool.AccessibilityServiceTool;
import org.autojs.autojs.ui.floating.FloatyWindowManger;
import org.autojs.autojs.ui.floating.FullScreenFloatyWindow;
import org.autojs.autojs.ui.floating.layoutinspector.LayoutBoundsFloatyWindow;
@@ -28,12 +32,6 @@ import org.autojs.autojs.ui.floating.layoutinspector.LayoutHierarchyFloatyWindow
import org.autojs.autojs.ui.log.LogActivity_;
import org.autojs.autojs.ui.settings.SettingsActivity_;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.view.accessibility.LayoutInspector;
import com.stardust.view.accessibility.NodeInfo;
import org.autojs.autojs.tool.AccessibilityServiceTool;
/**
* Created by Stardust on 2017/4/2.
@@ -59,31 +57,30 @@ public class AutoJs extends com.stardust.autojs.AutoJs {
FullScreenFloatyWindow create(NodeInfo nodeInfo);
}
private BroadcastReceiver mLayoutInspectBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
try {
ensureAccessibilityServiceEnabled();
String action = intent.getAction();
if (LayoutBoundsFloatyWindow.class.getName().equals(action)) {
capture(LayoutBoundsFloatyWindow::new);
} else if (LayoutHierarchyFloatyWindow.class.getName().equals(action)) {
capture(LayoutHierarchyFloatyWindow::new);
}
} catch (Exception e) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw e;
}
}
}
};
private AutoJs(final Application application) {
super(application);
getScriptEngineService().registerGlobalScriptExecutionListener(new ScriptExecutionGlobalListener());
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(LayoutBoundsFloatyWindow.class.getName());
intentFilter.addAction(LayoutHierarchyFloatyWindow.class.getName());
BroadcastReceiver mLayoutInspectBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
try {
ensureAccessibilityServiceEnabled();
String action = intent.getAction();
if (LayoutBoundsFloatyWindow.class.getName().equals(action)) {
capture(LayoutBoundsFloatyWindow::new);
} else if (LayoutHierarchyFloatyWindow.class.getName().equals(action)) {
capture(LayoutHierarchyFloatyWindow::new);
}
} catch (Exception e) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw e;
}
}
}
};
LocalBroadcastManager.getInstance(application).registerReceiver(mLayoutInspectBroadcastReceiver, intentFilter);
}
@@ -115,7 +112,7 @@ public class AutoJs extends com.stardust.autojs.AutoJs {
@Override
public String println(int level, CharSequence charSequence) {
String log = super.println(level, charSequence);
DevPluginService.getInstance().log(log);
new Thread(() -> DevPluginService.getInstance().print(log)).start();
return log;
}
};

View File

@@ -1,12 +1,16 @@
package org.autojs.autojs.autojs;
import android.annotation.SuppressLint;
import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.engine.JavaScriptEngine;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.execution.ScriptExecutionListener;
import org.autojs.autojs.App;
import com.stardust.autojs.runtime.api.Console;
import org.autojs.autojs.R;
import java.math.BigDecimal;
/**
* Created by Stardust on 2017/5/3.
*/
@@ -24,13 +28,26 @@ public class ScriptExecutionGlobalListener implements ScriptExecutionListener {
onFinish(execution);
}
@SuppressLint("DefaultLocale")
private void onFinish(ScriptExecution execution) {
Long millis = (Long) execution.getEngine().getTag(ENGINE_TAG_START_TIME);
if (millis == null)
return;
if (millis != null) {
printSeconds(execution, millis);
}
}
private void printSeconds(ScriptExecution execution, Long millis) {
double seconds = (System.currentTimeMillis() - millis) / 1000.0;
AutoJs.getInstance().getScriptEngineService().getGlobalConsole()
.verbose(GlobalAppContext.getString(R.string.text_execution_finished), execution.getSource().toString(), seconds);
@SuppressLint("DefaultLocale")
BigDecimal secondsString = new BigDecimal(String.format("%.3f", seconds)).stripTrailingZeros();
printSeconds(execution, secondsString);
}
private void printSeconds(ScriptExecution execution, BigDecimal seconds) {
Console console = AutoJs.getInstance().getScriptEngineService().getGlobalConsole();
console.verbose(GlobalAppContext.getString(R.string.text_execution_finished), execution.getSource().toString(), seconds);
}
@Override

View File

@@ -49,7 +49,7 @@ public abstract class LayoutInspectTileService extends TileService implements La
public void onClick() {
super.onClick();
Log.d(getClass().getName(), "onClick");
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
// sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
if (AccessibilityService.Companion.getInstance() == null) {
Toast.makeText(this, R.string.text_no_accessibility_permission_to_capture, Toast.LENGTH_SHORT).show();
AccessibilityServiceTool.goToAccessibilitySetting();
@@ -83,7 +83,6 @@ public abstract class LayoutInspectTileService extends TileService implements La
inactive();
}
});
}
protected abstract FullScreenFloatyWindow onCreateWindow(NodeInfo capture);

View File

@@ -1,5 +1,7 @@
package org.autojs.autojs.model.explorer;
import androidx.annotation.NonNull;
import com.stardust.pio.PFile;
import com.stardust.util.ObjectHelper;
import com.stardust.util.Objects;
@@ -18,7 +20,7 @@ public class ExplorerFileItem implements ExplorerItem {
"js", "java", "xml", "json", "txt", "log", "ts"
));
private PFile mFile;
private final PFile mFile;
private final ExplorerPage mParent;
public ExplorerFileItem(PFile file, ExplorerPage parent) {
@@ -104,6 +106,7 @@ public class ExplorerFileItem implements ExplorerItem {
return type.equals("js") || type.equals("auto");
}
@NonNull
@Override
public String toString() {
return getClass().getSimpleName() + "{" +

View File

@@ -0,0 +1,138 @@
package org.autojs.autojs.pluginclient;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class Buffer {
public final int length;
public final byte[] bytes;
public Buffer(int length) {
this.bytes = new byte[length];
this.length = this.bytes.length;
}
public Buffer(byte[] bytes) {
this.bytes = bytes;
if (this.bytes != null) {
this.length = this.bytes.length;
} else {
this.length = 0;
}
}
public int readInt8(int offset) {
return ((int) this.bytes[offset] & 0xff);
}
public int readInt16BE(int offset) {
return (((int) this.bytes[offset + 2] & 0xff) << 8) |
((int) this.bytes[offset + 3] & 0xff);
}
public int readInt16LE(int offset) {
return ((int) this.bytes[offset] & 0xff) |
(((int) this.bytes[offset + 1] & 0xff) << 8);
}
public int readInt32BE(int offset) {
return (((int) this.bytes[offset] & 0xff) << 24) |
(((int) this.bytes[offset + 1] & 0xff) << 16) |
(((int) this.bytes[offset + 2] & 0xff) << 8) |
((int) this.bytes[offset + 3] & 0xff);
}
public int readInt32LE(int offset) {
return ((int) this.bytes[offset] & 0xff) |
(((int) this.bytes[offset + 1] & 0xff) << 8) |
(((int) this.bytes[offset + 2] & 0xff) << 16) |
(((int) this.bytes[offset + 3] & 0xff) << 24);
}
public int readUInt8(int offset) {
return this.readInt8(offset);
}
public int readUInt16BE(int offset) {
return this.readInt16BE(offset);
}
public int readUInt16LE(int offset) {
return this.readInt16LE(offset);
}
public int readUInt32BE(int offset) {
return this.readInt32BE(offset);
}
public int readUInt32LE(int offset) {
return this.readInt32LE(offset);
}
public void writeInt8(int value, int offset) {
this.bytes[offset] = (byte) (value & 0xffL);
}
public void writeInt16BE(int value, int offset) {
this.bytes[offset] = (byte) ((value >>> 8L) & 0xffL);
this.bytes[offset + 1] = (byte) (value & 0xffL);
}
public void writeInt16LE(int value, int offset) {
this.bytes[offset] = (byte) (value & 0xffL);
this.bytes[offset + 1] = (byte) ((value >>> 8L) & 0xffL);
}
public void writeInt32BE(int value, int offset) {
this.bytes[offset] = (byte) ((value >>> 24L) & 0xffL);
this.bytes[offset + 1] = (byte) ((value >>> 16L) & 0xffL);
this.bytes[offset + 2] = (byte) ((value >>> 8L) & 0xffL);
this.bytes[offset + 3] = (byte) (value & 0xffL);
}
public void writeInt32LE(int value, int offset) {
this.bytes[offset] = (byte) (value & 0xffL);
this.bytes[offset + 1] = (byte) ((value >>> 8L) & 0xffL);
this.bytes[offset + 2] = (byte) ((value >>> 16L) & 0xffL);
this.bytes[offset + 3] = (byte) ((value >>> 24L) & 0xffL);
}
public void writeUInt8(int value, int offset) {
this.writeInt8(value, offset);
}
public void writeUInt16BE(int value, int offset) {
this.writeInt16BE(value, offset);
}
public void writeUInt16LE(int value, int offset) {
this.writeInt16LE(value, offset);
}
public void writeUInt32BE(int value, int offset) {
this.writeInt32BE(value, offset);
}
public void writeUInt32LE(int value, int offset) {
this.writeInt32LE(value, offset);
}
public Buffer slice(int start, int end) {
int len = end - start;
if (len <= 0) {
return null;
}
ByteBuffer buffer = ByteBuffer.wrap(this.bytes, start, len);
return new Buffer(buffer.array());
}
@NonNull
@Override
public String toString() {
return StandardCharsets.UTF_8.decode(ByteBuffer.wrap(this.bytes)).toString();
}
}

View File

@@ -2,7 +2,6 @@ package org.autojs.autojs.pluginclient;
import android.annotation.SuppressLint;
import android.text.TextUtils;
import android.widget.Toast;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
@@ -37,8 +36,7 @@ import io.reactivex.schedulers.Schedulers;
public class DevPluginResponseHandler implements Handler {
private Router mRouter = new Router.RootRouter("type")
private final Router mRouter = new Router.RootRouter("type")
.handler("command", new Router("command")
.handler("run", data -> {
String script = data.get("script").getAsString();
@@ -80,10 +78,10 @@ public class DevPluginResponseHandler implements Handler {
return true;
}));
private HashMap<String, ScriptExecution> mScriptExecutions = new HashMap<>();
private final HashMap<String, ScriptExecution> mScriptExecutions = new HashMap<>();
private final File mCacheDir;
@SuppressWarnings("ResultOfMethodCallIgnored")
public DevPluginResponseHandler(File cacheDir) {
mCacheDir = cacheDir;
if (cacheDir.exists()) {
@@ -101,14 +99,15 @@ public class DevPluginResponseHandler implements Handler {
return mRouter.handle(data);
}
public Observable<File> handleBytes(JsonObject data, JsonWebSocket.Bytes bytes) {
public Observable<File> handleBytes(JsonObject data, JsonSocket.Bytes bytes) {
String id = data.get("data").getAsJsonObject().get("id").getAsString();
String idMd5 = MD5.md5(id);
return Observable.fromCallable(() -> {
File dir = new File(mCacheDir, idMd5);
Zip.unzip(new ByteArrayInputStream(bytes.byteString.toByteArray()), dir);
return dir;
})
return Observable
.fromCallable(() -> {
File dir = new File(mCacheDir, idMd5);
Zip.unzip(new ByteArrayInputStream(bytes.byteString.toByteArray()), dir);
return dir;
})
.subscribeOn(Schedulers.io());
}
@@ -121,7 +120,6 @@ public class DevPluginResponseHandler implements Handler {
mScriptExecutions.put(viewId, Scripts.INSTANCE.run(new StringScriptSource("[remote]" + name, script)));
}
private void launchProject(String dir) {
try {
new ProjectLauncher(dir)
@@ -132,7 +130,6 @@ public class DevPluginResponseHandler implements Handler {
}
}
private void stopScript(String viewId) {
ScriptExecution execution = mScriptExecutions.get(viewId);
if (execution != null) {
@@ -163,7 +160,7 @@ public class DevPluginResponseHandler implements Handler {
GlobalAppContext.toast(R.string.text_script_save_successfully);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@SuppressLint("CheckResult")
private void saveProject(String name, String dir) {
if (TextUtils.isEmpty(name)) {
@@ -171,19 +168,20 @@ public class DevPluginResponseHandler implements Handler {
}
name = PFiles.getNameWithoutExtension(name);
File toDir = new File(Pref.getScriptDirPath(), name);
Observable.fromCallable(() -> {
copyDir(new File(dir), toDir);
return toDir.getPath();
}).subscribeOn(Schedulers.io())
Observable
.fromCallable(() -> {
copyDir(new File(dir), toDir);
return toDir.getPath();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dest ->
GlobalAppContext.toast(R.string.text_project_save_success, dest),
err ->
GlobalAppContext.toast(R.string.text_project_save_error, err.getMessage())
);
.subscribe(dest -> GlobalAppContext.toast(R.string.text_project_save_success, dest),
err -> GlobalAppContext.toast(R.string.text_project_save_error, err.getMessage())
);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void copyDir(File fromDir, File toDir) throws FileNotFoundException {
toDir.mkdirs();
File[] files = fromDir.listFiles();

View File

@@ -1,34 +1,22 @@
package org.autojs.autojs.pluginclient;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.AnyThread;
import androidx.annotation.MainThread;
import androidx.annotation.WorkerThread;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.stardust.app.GlobalAppContext;
import com.stardust.util.MapBuilder;
import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.tool.ThreadTool;
import java.io.File;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.io.IOException;
import java.net.ServerSocket;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/**
* Created by Stardust on 2017/5/11.
@@ -36,11 +24,9 @@ import okhttp3.Request;
public class DevPluginService {
private static final int CLIENT_VERSION = 2;
private static final String LOG_TAG = "DevPluginService";
private static final String TYPE_HELLO = "hello";
private static final String TYPE_BYTES_COMMAND = "bytes_command";
private static final long HANDSHAKE_TIMEOUT = 10 * 1000;
public static DevPluginService getInstance() {
return sInstance;
}
public static class State {
@@ -69,234 +55,121 @@ public class DevPluginService {
}
}
private static final int PORT = 9317;
private static DevPluginService sInstance = new DevPluginService();
private final PublishSubject<State> mConnectionState = PublishSubject.create();
private final DevPluginResponseHandler mResponseHandler;
private final HashMap<String, JsonWebSocket.Bytes> mBytes = new HashMap<>();
private final HashMap<String, JsonObject> mRequiredBytesCommands = new HashMap<>();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private volatile JsonWebSocket mSocket;
public static DevPluginService getInstance() {
return sInstance;
@SuppressWarnings("unused")
public static class Port {
static int PC_CLIENT = 27139;
static int PC_SERVER = 6347;
static int AJ_CLIENT = -1;
static int AJ_SERVER = 9317;
}
@SuppressWarnings("unused")
public static class Version {
static int CLIENT = 2;
static int SERVER = 3;
}
public static final String TYPE_HELLO = "hello";
public static final String TYPE_BYTES_COMMAND = "bytes_command";
public static final int HANDSHAKE_TIMEOUT = JsonSocket.HANDSHAKE_TIMEOUT;
private static final DevPluginService sInstance = new DevPluginService();
public final DevPluginResponseHandler mResponseHandler;
public final Handler mHandler = new Handler(Looper.getMainLooper());
private volatile JsonSocketClient mJsonSocketClient;
private volatile JsonSocketServer mJsonSocketServer;
private volatile ServerSocket mAJServerSocket;
public DevPluginService() {
File cache = new File(GlobalAppContext.get().getCacheDir(), "remote_project");
mResponseHandler = new DevPluginResponseHandler(cache);
}
@AnyThread
public boolean isConnected() {
return mSocket != null && !mSocket.isClosed();
@Nullable
public JsonSocketClient getJsonSocketClient() {
return mJsonSocketClient;
}
@Nullable
public JsonSocketServer getJsonSocketServer() {
return mJsonSocketServer;
}
@AnyThread
public boolean isDisconnected() {
return mSocket == null || mSocket.isClosed();
}
@AnyThread
public void disconnectIfNeeded() {
if (isDisconnected())
return;
disconnect();
}
@AnyThread
public void disconnect() {
mSocket.close();
mSocket = null;
}
public Observable<State> connectionState() {
return mConnectionState;
}
@AnyThread
public Observable<JsonWebSocket> connectToServer(String host) {
int port = PORT;
public Observable<JsonSocketClient> connectToRemoteServer(String host) {
int port = Port.PC_SERVER;
String ip = host;
int i = host.lastIndexOf(':');
if (i > 0 && i < host.length() - 1) {
port = Integer.parseInt(host.substring(i + 1));
ip = host.substring(0, i);
}
mConnectionState.onNext(new State(State.CONNECTING));
return socket(ip, port)
.observeOn(AndroidSchedulers.mainThread())
.doOnError(this::onSocketError);
}
@AnyThread
private Observable<JsonWebSocket> socket(String ip, int port) {
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(0, TimeUnit.MILLISECONDS)
.build();
String url = ip + ":" + port;
if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
url = "ws://" + url;
}
return Observable.just(new JsonWebSocket(client, new Request.Builder()
.url(url)
.build()))
.doOnNext(socket -> {
mSocket = socket;
subscribeMessage(socket);
sayHelloToServer(socket);
return Observable
.just(new JsonSocketClient(ip, port))
.observeOn(Schedulers.newThread())
.doOnNext(jsonSocketClient -> {
try {
mJsonSocketClient = jsonSocketClient;
if (ThreadTool.wait(jsonSocketClient::isSocketReady, HANDSHAKE_TIMEOUT)) {
jsonSocketClient
.subscribeMessage()
.monitorMessage()
.sayHello();
} else {
jsonSocketClient.onHandshakeTimeout();
}
} catch (IOException e) {
jsonSocketClient.onSocketError(e);
}
});
}
@SuppressLint("CheckResult")
private void subscribeMessage(JsonWebSocket socket) {
socket.data()
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(() -> mConnectionState.onNext(new State(State.DISCONNECTED)))
.subscribe(data -> onSocketData(socket, data), this::onSocketError);
socket.bytes()
.doOnComplete(() -> mConnectionState.onNext(new State(State.DISCONNECTED)))
.subscribe(data -> onSocketData(socket, data), this::onSocketError);
}
@MainThread
private void onSocketError(Throwable e) {
e.printStackTrace();
if (mSocket != null) {
mConnectionState.onNext(new State(State.DISCONNECTED, e));
mSocket.close();
mSocket = null;
}
}
@MainThread
private void onSocketData(JsonWebSocket jsonWebSocket, JsonElement element) {
if (!element.isJsonObject()) {
Log.w(LOG_TAG, "onSocketData: not json object: " + element);
return;
}
try {
JsonObject obj = element.getAsJsonObject();
JsonElement typeElement = obj.get("type");
if (typeElement == null || !typeElement.isJsonPrimitive()) {
return;
}
String type = typeElement.getAsString();
if (type.equals(TYPE_HELLO)) {
onServerHello(jsonWebSocket, obj);
return;
}
if (TYPE_BYTES_COMMAND.equals(type)) {
String md5 = obj.get("md5").getAsString();
JsonWebSocket.Bytes bytes = mBytes.remove(md5);
if (bytes != null) {
handleBytes(obj, bytes);
} else {
mRequiredBytesCommands.put(md5, obj);
}
return;
}
mResponseHandler.handle(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
@SuppressLint("CheckResult")
private void handleBytes(JsonObject obj, JsonWebSocket.Bytes bytes) {
mResponseHandler.handleBytes(obj, bytes)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dir -> {
obj.get("data").getAsJsonObject().add("dir", new JsonPrimitive(dir.getPath()));
mResponseHandler.handle(obj);
@AnyThread
public Observable<JsonSocketServer> enableLocalServer() {
return Observable
.just(new JsonSocketServer(Port.AJ_SERVER))
.observeOn(Schedulers.newThread())
.doOnNext(jsonSocketServer -> {
try {
mJsonSocketServer = jsonSocketServer;
mAJServerSocket = jsonSocketServer.getServerSocket();
if (mAJServerSocket != null) {
jsonSocketServer
.setStateConnected()
.setSocket(mAJServerSocket.accept())
.subscribeMessage()
.monitorMessage()
.sayHello();
} else {
jsonSocketServer.onHandshakeTimeout();
}
} catch (IOException e) {
jsonSocketServer.onSocketError(e);
}
});
}
@WorkerThread
private void onSocketData(JsonWebSocket jsonWebSocket, JsonWebSocket.Bytes bytes) {
JsonObject command = mRequiredBytesCommands.remove(bytes.md5);
if (command != null) {
handleBytes(command, bytes);
} else {
mBytes.put(bytes.md5, bytes);
public static void setState(PublishSubject<State> cxn, int state) {
cxn.onNext(new State(state));
}
public static void setState(PublishSubject<State> cxn, int state, Throwable e) {
cxn.onNext(new State(state, e));
}
@AnyThread
// FIXME by SuperMonster003 on Dec 29, 2021
// ! Would print double (may be even more times) the amount of
// ! messages on VSCode when multi connection were established.
public void print(String log) {
if (mJsonSocketClient != null) {
mJsonSocketClient.writeLog(log);
}
if (mJsonSocketServer != null) {
mJsonSocketServer.writeLog(log);
}
}
@WorkerThread
private void sayHelloToServer(JsonWebSocket socket) {
writeMap(socket, TYPE_HELLO, new MapBuilder<String, Object>()
.put("device_name", Build.BRAND + " " + Build.MODEL)
.put("client_version", CLIENT_VERSION)
.put("app_version", BuildConfig.VERSION_NAME)
.put("app_version_code", BuildConfig.VERSION_CODE)
.build());
mHandler.postDelayed(() -> {
if (mSocket != socket && !socket.isClosed()) {
onHandshakeTimeout(socket);
}
}, HANDSHAKE_TIMEOUT);
}
@MainThread
private void onHandshakeTimeout(JsonWebSocket socket) {
Log.i(LOG_TAG, "onHandshakeTimeout");
mConnectionState.onNext(new State(State.DISCONNECTED, new SocketTimeoutException("handshake timeout")));
socket.close();
}
@MainThread
private void onServerHello(JsonWebSocket jsonWebSocket, JsonObject message) {
Log.i(LOG_TAG, "onServerHello: " + message);
mSocket = jsonWebSocket;
mConnectionState.onNext(new State(State.CONNECTED));
}
@AnyThread
private static boolean write(JsonWebSocket socket, String type, JsonObject data) {
JsonObject json = new JsonObject();
json.addProperty("type", type);
json.add("data", data);
return socket.write(json);
}
@AnyThread
private static boolean writePair(JsonWebSocket socket, String type, Pair<String, String> pair) {
JsonObject data = new JsonObject();
data.addProperty(pair.first, pair.second);
return write(socket, type, data);
}
@AnyThread
private static boolean writeMap(JsonWebSocket socket, String type, Map<String, ?> map) {
JsonObject data = new JsonObject();
for (Map.Entry<String, ?> entry : map.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
data.addProperty(entry.getKey(), (String) value);
} else if (value instanceof Character) {
data.addProperty(entry.getKey(), (Character) value);
} else if (value instanceof Number) {
data.addProperty(entry.getKey(), (Number) value);
} else if (value instanceof Boolean) {
data.addProperty(entry.getKey(), (Boolean) value);
} else if (value instanceof JsonElement) {
data.add(entry.getKey(), (JsonElement) value);
} else {
throw new IllegalArgumentException("cannot put value " + value + " into json");
}
}
return write(socket, type, data);
}
@SuppressLint("CheckResult")
@AnyThread
public void log(String log) {
if (!isConnected())
return;
writePair(mSocket, "log", new Pair<>("log", log));
}
}
}

View File

@@ -0,0 +1,283 @@
package org.autojs.autojs.pluginclient;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;
import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.runtime.api.Device;
import com.stardust.util.MapBuilder;
import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.tool.IOTool;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.subjects.PublishSubject;
import okio.ByteString;
abstract public class JsonSocket extends Socket {
private final String TAG = "JsonSocket";
public static final int HEADER_SIZE = 8;
public static final int HANDSHAKE_TIMEOUT = 5 * 1000;
public static final String TYPE_HELLO = DevPluginService.TYPE_HELLO;
public static final String TYPE_BYTES_COMMAND = DevPluginService.TYPE_BYTES_COMMAND;
public static class Bytes {
public final String md5;
public final ByteString byteString;
public final long timestamp;
public Bytes(String md5, ByteString byteString) {
this.md5 = md5;
this.byteString = byteString;
this.timestamp = System.currentTimeMillis();
}
}
@SuppressWarnings("unused")
public static class Type {
public static int TEXT = 1;
public static int BINARY = 2;
public static int GZIP_TEXT = 3;
public static int GZIP_BINARY = 4;
}
public final android.os.Handler mHandler = new Handler(Looper.getMainLooper());
public final DevPluginService devPlugin = DevPluginService.getInstance();
public abstract void switchOff() throws IOException;
public abstract boolean isSocketReady();
public abstract Socket getSocket();
public abstract JsonSocket setSocket(Socket socket);
public abstract JsonSocket monitorMessage();
public abstract JsonSocket subscribeMessage();
public abstract JsonSocket setStateConnected();
public abstract PublishSubject<JsonElement> getJsonElementPublishSubject();
public abstract PublishSubject<Bytes> getBytesPublishSubject();
public void sayHello() {
writeMap(TYPE_HELLO, new MapBuilder<String, Object>()
.put("device_name", Build.BRAND + " " + Build.MODEL)
.put("app_version", BuildConfig.VERSION_NAME)
.put("app_version_code", BuildConfig.VERSION_CODE)
.put("server_version", DevPluginService.Version.SERVER)
.put("device_id", new Device(GlobalAppContext.get()).getAndroidId())
.build());
}
public void onMessage(JsonSocket jsonSocket, String text) {
Log.d(TAG, "onMessage: text = " + text);
dispatchJson(jsonSocket, text);
}
public void onMessage(JsonSocket jsonSocket, ByteString bytes) {
Log.d(TAG, "onMessage: ByteString = " + bytes.toString());
jsonSocket.getBytesPublishSubject().onNext(new Bytes(bytes.md5().hex(), bytes));
}
private void onMessageDispatch(JsonSocket jsonSocket, String str) throws IOException {
Log.d(TAG, "Input total str: " + str);
Log.d(TAG, "Input total length: " + str.length());
String header = str.substring(0, HEADER_SIZE);
Log.d(TAG, "Input data length: " + new Buffer(header.getBytes()).readInt32BE(0));
Log.d(TAG, "Input data type: " + new Buffer(header.getBytes()).readInt32BE(4));
String message = str.substring(HEADER_SIZE);
Log.d(TAG, "Input message length: " + message.length());
Log.d(TAG, "Input message: " + message);
// Log.d(TAG, "Input message gunzip: " + gunzip(str));
onMessage(jsonSocket, message);
}
@SuppressWarnings("SameParameterValue")
private void writeMap(String type, Map<String, ?> map) {
JsonObject data = new JsonObject();
for (Map.Entry<String, ?> entry : map.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
data.addProperty(entry.getKey(), (String) value);
} else if (value instanceof Character) {
data.addProperty(entry.getKey(), (Character) value);
} else if (value instanceof Number) {
data.addProperty(entry.getKey(), (Number) value);
} else if (value instanceof Boolean) {
data.addProperty(entry.getKey(), (Boolean) value);
} else if (value instanceof JsonElement) {
data.add(entry.getKey(), (JsonElement) value);
} else {
throw new IllegalArgumentException("cannot put value " + value + " into json");
}
}
writeData(type, data);
}
@SuppressWarnings("SameParameterValue")
@AnyThread
public void writePair(String type, Pair<String, String> pair) {
JsonObject data = new JsonObject();
data.addProperty(pair.first, pair.second);
writeData(type, data);
}
public void writeLog(String log) {
if (isSocketReady()) {
writePair("log", new Pair<>("log", log));
}
}
private void writeData(String type, JsonObject data) {
JsonObject json = new JsonObject();
json.addProperty("type", type);
json.add("data", data);
writeMessage(json);
}
private void writeMessage(JsonElement element) {
String json = element.toString();
Log.d(TAG, "writeMessage: length = " + json.length() + ", json = " + element);
try {
writeMessageWithType(getSocket(), json, Type.TEXT);
} catch (IOException e) {
e.printStackTrace();
}
}
private void writeMessageWithType(Socket socket, String message, int messageType) throws IOException {
if (socket != null) {
byte[] jsonBytes = getJsonBytes(message);
byte[] headerBytes = getHeaderBytes(new int[]{jsonBytes.length, messageType});
OutputStream os = socket.getOutputStream();
BufferedOutputStream writer = new BufferedOutputStream(os);
writer.write(headerBytes);
writer.write(jsonBytes);
writer.flush();
}
}
public void monitorMessage(Socket socket, JsonSocket jsonSocket) {
new Thread(() -> {
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try {
inputStream = socket.getInputStream();
inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
bufferedReader = new BufferedReader(inputStreamReader);
final StringBuilder stringBuilder = new StringBuilder();
String readLine;
Log.d(TAG, "bufferedReader is reading lines...");
while ((readLine = bufferedReader.readLine()) != null && !socket.isClosed()) {
Log.d(TAG, "Reading line...");
stringBuilder.append(readLine);
Log.d(TAG, "read line length: " + stringBuilder.toString().length());
onMessageDispatch(jsonSocket, stringBuilder.toString());
stringBuilder.setLength(0);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
IOTool.close(bufferedReader);
IOTool.close(inputStreamReader);
IOTool.close(inputStream);
try {
jsonSocket.switchOff();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public void setState(PublishSubject<DevPluginService.State> cxn, int state) {
cxn.onNext(new DevPluginService.State(state));
}
public void setState(PublishSubject<DevPluginService.State> cxn, int state, Throwable e) {
cxn.onNext(new DevPluginService.State(state, e));
}
private void dispatchJson(@NonNull JsonSocket jsonSocket, String json) {
try {
Log.d(TAG, "JSON to parse: " + json);
JsonReader reader = new JsonReader(new StringReader(json));
reader.setLenient(true);
JsonElement element = JsonParser.parseReader(reader);
jsonSocket.getJsonElementPublishSubject().onNext(element);
} catch (JsonParseException e) {
e.printStackTrace();
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@SuppressLint("CheckResult")
public void handleBytes(JsonObject jsonObject, JsonSocket.Bytes bytes) {
devPlugin.mResponseHandler
.handleBytes(jsonObject, bytes)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dir -> {
jsonObject
.get("data")
.getAsJsonObject()
.add("dir", new JsonPrimitive(dir.getPath()));
devPlugin.mResponseHandler.handle(jsonObject);
});
}
private byte[] getJsonBytes(@NonNull String json) {
return json.getBytes(StandardCharsets.UTF_8);
}
@NonNull
private byte[] getHeaderBytes(@NonNull int[] data) {
// byte order is big endian
// use Buffer#readInt32BE for a socket server in Node.js
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * data.length);
for (int i : data) {
// int, 4 bytes
buffer.putInt(i);
}
return buffer.array();
}
}

View File

@@ -0,0 +1,205 @@
package org.autojs.autojs.pluginclient;
import android.annotation.SuppressLint;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.WorkerThread;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.subjects.PublishSubject;
public class JsonSocketClient extends JsonSocket {
private static final String TAG = JsonSocketClient.class.getSimpleName();
public static final PublishSubject<DevPluginService.State> cxnState = PublishSubject.create();
private final PublishSubject<JsonElement> mJsonElementPublishSubject = PublishSubject.create();
private final PublishSubject<Bytes> mBytesPublishSubject = PublishSubject.create();
private final HashMap<String, Bytes> mBytes = new HashMap<>();
private final HashMap<String, JsonObject> mRequiredBytesCommands = new HashMap<>();
private Socket mSocket;
// @Constructor
public JsonSocketClient(String host, int port) {
new Thread(() -> {
try {
setStateConnecting();
mSocket = new Socket(host, port);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
public boolean isSocketReady() {
return mSocket != null && mSocket.isConnected();
}
@Override
public Socket getSocket() {
return mSocket;
}
@Override
public JsonSocket setSocket(Socket socket) {
mSocket = socket;
return this;
}
@Override
public PublishSubject<JsonElement> getJsonElementPublishSubject() {
return mJsonElementPublishSubject;
}
@Override
public PublishSubject<Bytes> getBytesPublishSubject() {
return mBytesPublishSubject;
}
@Override
public void switchOff() throws IOException {
close();
setStateDisconnected();
}
public void close() throws IOException {
Log.w(TAG, "closing socket...");
mJsonElementPublishSubject.onComplete();
if (mSocket != null) {
mSocket.close();
mSocket = null;
}
}
@Override
public void sayHello() {
super.sayHello();
mHandler.postDelayed(() -> {
if (!isSocketReady()) {
try {
onHandshakeTimeout();
} catch (IOException e) {
e.printStackTrace();
}
}
}, HANDSHAKE_TIMEOUT);
}
private void onHello(JsonObject message) {
Log.i(TAG, "onHello: " + message);
setStateConnected();
}
@MainThread
private void onSocketData(JsonElement element) {
Log.d(TAG, "onSocketData...");
try {
if (!element.isJsonObject()) {
onSocketError(new Error("Not a JSON object"));
return;
}
JsonObject obj = element.getAsJsonObject();
JsonElement typeElement = obj.get("type");
if (typeElement == null || !typeElement.isJsonPrimitive()) {
return;
}
String type = typeElement.getAsString();
Log.d(TAG, "json type: " + type);
switch (type) {
case TYPE_HELLO -> onHello(obj);
case TYPE_BYTES_COMMAND -> {
String md5 = obj.get("md5").getAsString();
JsonSocket.Bytes bytes = mBytes.remove(md5);
if (bytes != null) {
handleBytes(obj, bytes);
} else {
mRequiredBytesCommands.put(md5, obj);
}
}
default -> devPlugin.mResponseHandler.handle(obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@WorkerThread
private void onSocketData(JsonSocket.Bytes bytes) {
Log.d(TAG, "onSocketData bytes");
JsonObject command = mRequiredBytesCommands.remove(bytes.md5);
if (command != null) {
handleBytes(command, bytes);
} else {
mBytes.put(bytes.md5, bytes);
}
}
@MainThread
public void onSocketError(Throwable e) throws IOException {
Log.w(TAG, "onSocketError");
e.printStackTrace();
setStateDisconnected(e);
close();
}
@MainThread
public void onHandshakeTimeout() throws IOException {
Log.i(TAG, "onHandshakeTimeout");
// setStateDisconnected(new SocketTimeoutException("handshake timeout"));
setStateDisconnected();
close();
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@SuppressLint("CheckResult")
@Override
public JsonSocket subscribeMessage() {
mJsonElementPublishSubject
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(this::setStateDisconnected)
.subscribe(this::onSocketData, this::onSocketError);
mBytesPublishSubject
.doOnComplete(this::setStateDisconnected)
.subscribe(this::onSocketData, this::onSocketError);
return this;
}
public JsonSocket monitorMessage() {
super.monitorMessage(mSocket, this);
return this;
}
public JsonSocket setStateConnected() {
setState(cxnState, DevPluginService.State.CONNECTED);
return this;
}
public JsonSocket setStateConnecting() {
setState(cxnState, DevPluginService.State.CONNECTING);
return this;
}
public JsonSocket setStateDisconnected() {
setState(cxnState, DevPluginService.State.DISCONNECTED);
return this;
}
public JsonSocket setStateDisconnected(Throwable e) {
setState(cxnState, DevPluginService.State.DISCONNECTED, e);
return this;
}
}

View File

@@ -0,0 +1,212 @@
package org.autojs.autojs.pluginclient;
import android.annotation.SuppressLint;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.WorkerThread;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.subjects.PublishSubject;
public class JsonSocketServer extends JsonSocket {
private static final String TAG = JsonSocketServer.class.getSimpleName();
public static final PublishSubject<DevPluginService.State> cxnState = PublishSubject.create();
private final PublishSubject<JsonElement> mJsonElementPublishSubject = PublishSubject.create();
private final PublishSubject<Bytes> mBytesPublishSubject = PublishSubject.create();
private final HashMap<String, Bytes> mBytes = new HashMap<>();
private final HashMap<String, JsonObject> mRequiredBytesCommands = new HashMap<>();
private Socket mSocket;
private ServerSocket mServerSocket;
// @Constructor
public JsonSocketServer(int port) {
try {
setStateConnecting();
mServerSocket = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@SuppressLint("CheckResult")
@Override
public JsonSocket subscribeMessage() {
mJsonElementPublishSubject
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(this::setStateDisconnected)
.subscribe(this::onSocketData, this::onSocketError);
mBytesPublishSubject
.doOnComplete(this::setStateDisconnected)
.subscribe(this::onSocketData, this::onSocketError);
return this;
}
public boolean isSocketReady() {
return mSocket != null && !mSocket.isClosed();
}
public boolean isServerSocketReady() {
return mServerSocket != null && !mServerSocket.isClosed();
}
@Override
public Socket getSocket() {
return mSocket;
}
public ServerSocket getServerSocket() {
return mServerSocket;
}
@Override
public JsonSocket setSocket(Socket socket) {
mSocket = socket;
return this;
}
@Override
public PublishSubject<JsonElement> getJsonElementPublishSubject() {
return mJsonElementPublishSubject;
}
@Override
public PublishSubject<Bytes> getBytesPublishSubject() {
return mBytesPublishSubject;
}
@Override
public void switchOff() throws IOException {
if (isServerSocketReady()) {
mServerSocket.close();
mServerSocket = null;
}
close();
setStateDisconnected();
}
public void close() throws IOException {
if (isSocketReady()) {
mSocket.close();
mSocket = null;
}
}
@Override
public void sayHello() {
super.sayHello();
mHandler.postDelayed(() -> {
if (!isServerSocketReady()) {
try {
onHandshakeTimeout();
} catch (IOException e) {
e.printStackTrace();
}
}
}, HANDSHAKE_TIMEOUT);
}
public JsonSocket monitorMessage() {
super.monitorMessage(mSocket, this);
return this;
}
@MainThread
private void onSocketData(JsonElement element) {
Log.d(TAG, "onSocketData...");
try {
if (!element.isJsonObject()) {
onSocketError(new Error("Not a JSON object"));
return;
}
JsonObject obj = element.getAsJsonObject();
JsonElement typeElement = obj.get("type");
if (typeElement == null || !typeElement.isJsonPrimitive()) {
return;
}
String type = typeElement.getAsString();
Log.d(TAG, "json type: " + type);
switch (type) {
case TYPE_HELLO -> setStateConnected();
case TYPE_BYTES_COMMAND -> {
String md5 = obj.get("md5").getAsString();
Bytes bytes = mBytes.remove(md5);
if (bytes != null) {
handleBytes(obj, bytes);
} else {
mRequiredBytesCommands.put(md5, obj);
}
}
default -> devPlugin.mResponseHandler.handle(obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@WorkerThread
private void onSocketData(Bytes bytes) {
Log.d(TAG, "onSocketData bytes");
JsonObject command = mRequiredBytesCommands.remove(bytes.md5);
if (command != null) {
handleBytes(command, bytes);
} else {
mBytes.put(bytes.md5, bytes);
}
}
@MainThread
public void onSocketError(Throwable e) throws IOException {
e.printStackTrace();
if (isServerSocketReady()) {
setStateDisconnected(e);
switchOff();
}
}
@MainThread
public void onHandshakeTimeout() throws IOException {
Log.i(TAG, "onHandshakeTimeout");
// setStateDisconnected(new SocketTimeoutException("handshake timeout"));
setStateDisconnected();
switchOff();
}
public JsonSocket setStateConnected() {
setState(cxnState, DevPluginService.State.CONNECTED);
return this;
}
public JsonSocket setStateConnecting() {
setState(cxnState, DevPluginService.State.CONNECTING);
return this;
}
public JsonSocket setStateDisconnected() {
setState(cxnState, DevPluginService.State.DISCONNECTED);
return this;
}
public JsonSocket setStateDisconnected(Throwable e) {
setState(cxnState, DevPluginService.State.DISCONNECTED, e);
return this;
}
}

View File

@@ -1,125 +0,0 @@
package org.autojs.autojs.pluginclient;
import android.util.Log;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
import java.io.StringReader;
import androidx.annotation.Nullable;
import io.reactivex.Observable;
import io.reactivex.subjects.PublishSubject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;
public class JsonWebSocket extends WebSocketListener {
public static class Bytes {
public final String md5;
public final ByteString byteString;
public final long timestamp;
public Bytes(String md5, ByteString byteString) {
this.md5 = md5;
this.byteString = byteString;
this.timestamp = System.currentTimeMillis();
}
}
private static final String LOG_TAG = "JsonWebSocket";
private final WebSocket mWebSocket;
private final JsonParser mJsonParser = new JsonParser();
private final PublishSubject<JsonElement> mJsonElementPublishSubject = PublishSubject.create();
private final PublishSubject<Bytes> mBytesPublishSubject = PublishSubject.create();
private volatile boolean mClosed = false;
public JsonWebSocket(OkHttpClient client, Request request) {
mWebSocket = client.newWebSocket(request, this);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
Log.d(LOG_TAG, "onMessage: text = " + text);
dispatchJson(text);
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
Log.d(LOG_TAG, "onMessage: ByteString = " + bytes.toString());
mBytesPublishSubject.onNext(new Bytes(bytes.md5().hex(), bytes));
}
public Observable<JsonElement> data() {
return mJsonElementPublishSubject;
}
public Observable<Bytes> bytes(){
return mBytesPublishSubject;
}
public boolean write(JsonElement element) {
String json = element.toString();
Log.d(LOG_TAG, "write: length = " + json.length() + ", json = " + element);
return mWebSocket.send(json);
}
public void close() {
mJsonElementPublishSubject.onComplete();
mClosed = true;
mWebSocket.close(1000, "close");
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
Log.d(LOG_TAG, "onFailure: code = " + code + ", reason = " + reason);
close();
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) {
Log.d(LOG_TAG, "onFailure: response = " + response, t);
close(t);
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
Log.d(LOG_TAG, "onOpen: response = " + response);
}
private void close(Throwable e) {
if (mClosed) {
return;
}
mJsonElementPublishSubject.onError(e);
mClosed = true;
mWebSocket.close(1011, "remote exception: " + e.getMessage());
}
private void dispatchJson(String json) {
try {
JsonReader reader = new JsonReader(new StringReader(json));
reader.setLenient(true);
JsonElement element = mJsonParser.parse(reader);
mJsonElementPublishSubject.onNext(element);
} catch (JsonParseException e) {
e.printStackTrace();
}
}
public boolean isClosed() {
return mClosed;
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.text.TextUtils;
import com.stardust.app.GlobalAppContext;
import org.autojs.autojs.Pref;
import org.autojs.autojs.R;
@@ -35,7 +36,7 @@ public class AccessibilityServiceTool {
public static void goToAccessibilitySetting() {
Context context = GlobalAppContext.get();
if (Pref.isFirstGoToAccessibilitySetting()) {
GlobalAppContext.toast(context.getString(R.string.text_please_choose) + context.getString(R.string.app_name));
GlobalAppContext.toast(context.getString(R.string.text_please_choose) + " " + context.getString(R.string.app_name));
}
try {
AccessibilityServiceUtils.INSTANCE.goToAccessibilitySetting(context);
@@ -44,16 +45,18 @@ public class AccessibilityServiceTool {
}
}
private static final String cmd = "enabled=$(settings get secure enabled_accessibility_services)\n" +
"pkg=%s\n" +
"if [[ $enabled == *$pkg* ]]\n" +
"then\n" +
"echo already_enabled\n" +
"else\n" +
"enabled=$pkg:$enabled\n" +
"settings put secure enabled_accessibility_services $enabled\n" +
"fi\n" +
"settings put secure accessibility_enabled 1";
private static final String cmd = """
enabled=$(settings get secure enabled_accessibility_services)
pkg=%s
if [[ $enabled == *$pkg* ]]
then
echo already_enabled
else
enabled=$pkg:$enabled
settings put secure enabled_accessibility_services $enabled
fi
settings put secure accessibility_enabled 1
""";
public static boolean enableAccessibilityServiceByRoot(Class<? extends android.accessibilityservice.AccessibilityService> accessibilityService) {
String serviceName = GlobalAppContext.get().getPackageName() + "/" + accessibilityService.getName();

View File

@@ -0,0 +1,81 @@
package org.autojs.autojs.tool;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class IOTool {
private static final String TAG = IOTool.class.getSimpleName();
public static void close(Closeable io) {
try {
if (io != null) {
io.close();
}
} catch (IOException e) {
Log.w(TAG, "ex: " + e);
}
}
public static void close(Closeable io, boolean exceptionMatters) throws IOException {
try {
if (io != null) {
io.close();
}
} catch (IOException e) {
if (exceptionMatters) {
throw e;
}
}
}
public static byte[] gzip(String str) {
ByteArrayOutputStream out = null;
GZIPOutputStream gzip = null;
try {
out = new ByteArrayOutputStream();
gzip = new GZIPOutputStream(out);
gzip.write(str.getBytes(StandardCharsets.UTF_8));
gzip.finish();
return out.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
close(out);
close(gzip);
}
return null;
}
public static String gunzip(byte[] bytes) {
ByteArrayOutputStream out = null;
GZIPInputStream gzip = null;
try {
out = new ByteArrayOutputStream();
gzip = new GZIPInputStream(new ByteArrayInputStream(bytes));
int res;
byte[] buf = new byte[1024];
while ((res = gzip.read(buf)) != -1) {
out.write(buf, 0, res);
}
out.flush();
return out.toString(String.valueOf(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
} finally {
close(out);
close(gzip);
}
return "";
}
}

View File

@@ -18,13 +18,15 @@ public class RootTool {
}
}
private static final String cmd = "enabled=$(settings get system pointer_location)\n" +
"if [[ $enabled == 1 ]]\n" +
"then\n" +
"settings put system pointer_location 0\n" +
"else\n" +
"settings put system pointer_location 1\n" +
"fi\n";
private static final String cmd = """
enabled=$(settings get system pointer_location)
if [[ $enabled == 1 ]]
then
settings put system pointer_location 0
else
settings put system pointer_location 1
fi
""";
public static void togglePointerLocation() {
try {

View File

@@ -1,136 +0,0 @@
package org.autojs.autojs.tool;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.math.BigDecimal;
import java.math.BigInteger;
public class SafeJsonElement extends JsonElement {
private final JsonElement mJsonElement;
public SafeJsonElement(JsonElement jsonElement) {
mJsonElement = jsonElement;
}
@Override
public JsonElement deepCopy() {
return null;
}
@Override
public boolean isJsonArray() {
return mJsonElement.isJsonArray();
}
@Override
public boolean isJsonObject() {
return mJsonElement.isJsonObject();
}
@Override
public boolean isJsonPrimitive() {
return mJsonElement.isJsonPrimitive();
}
@Override
public boolean isJsonNull() {
return mJsonElement.isJsonNull();
}
public JsonObject getAsJsonObject() {
return mJsonElement.getAsJsonObject();
}
public SafeJsonObject getAsSafeJsonObject() {
try {
return new SafeJsonObject(mJsonElement.getAsJsonObject());
} catch (Exception e) {
return null;
}
}
@Override
public JsonArray getAsJsonArray() {
return mJsonElement.getAsJsonArray();
}
@Override
public JsonPrimitive getAsJsonPrimitive() {
return mJsonElement.getAsJsonPrimitive();
}
@Override
public JsonNull getAsJsonNull() {
return mJsonElement.getAsJsonNull();
}
@Override
public boolean getAsBoolean() {
return mJsonElement.getAsBoolean();
}
@Override
public Number getAsNumber() {
return mJsonElement.getAsNumber();
}
@Override
public String getAsString() {
return mJsonElement.getAsString();
}
@Override
public double getAsDouble() {
return mJsonElement.getAsDouble();
}
@Override
public float getAsFloat() {
return mJsonElement.getAsFloat();
}
@Override
public long getAsLong() {
return mJsonElement.getAsLong();
}
@Override
public int getAsInt() {
return mJsonElement.getAsInt();
}
@Override
public byte getAsByte() {
return mJsonElement.getAsByte();
}
@Override
public char getAsCharacter() {
return mJsonElement.getAsCharacter();
}
@Override
public BigDecimal getAsBigDecimal() {
return mJsonElement.getAsBigDecimal();
}
@Override
public BigInteger getAsBigInteger() {
return mJsonElement.getAsBigInteger();
}
@Override
public short getAsShort() {
return mJsonElement.getAsShort();
}
@Override
public String toString() {
return mJsonElement.toString();
}
}

View File

@@ -1,161 +0,0 @@
package org.autojs.autojs.tool;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map;
import java.util.Set;
public class SafeJsonObject extends JsonElement {
private final JsonObject mJsonObject;
public SafeJsonObject(JsonObject jsonObject) {
mJsonObject = jsonObject;
}
public JsonObject deepCopy() {
return mJsonObject.deepCopy();
}
public void add(String property, JsonElement value) {
mJsonObject.add(property, value);
}
public JsonElement remove(String property) {
return mJsonObject.remove(property);
}
public void addProperty(String property, String value) {
mJsonObject.addProperty(property, value);
}
public void addProperty(String property, Number value) {
mJsonObject.addProperty(property, value);
}
public void addProperty(String property, Boolean value) {
mJsonObject.addProperty(property, value);
}
public void addProperty(String property, Character value) {
mJsonObject.addProperty(property, value);
}
public Set<Map.Entry<String, JsonElement>> entrySet() {
return mJsonObject.entrySet();
}
public Set<String> keySet() {
return mJsonObject.keySet();
}
public int size() {
return mJsonObject.size();
}
public boolean has(String memberName) {
return mJsonObject.has(memberName);
}
public JsonElement get(String memberName) {
return mJsonObject.get(memberName);
}
public JsonPrimitive getAsJsonPrimitive(String memberName) {
return mJsonObject.getAsJsonPrimitive(memberName);
}
public JsonArray getAsJsonArray(String memberName) {
return mJsonObject.getAsJsonArray(memberName);
}
public JsonObject getAsJsonObject(String memberName) {
return mJsonObject.getAsJsonObject(memberName);
}
public boolean isJsonArray() {
return mJsonObject.isJsonArray();
}
public boolean isJsonObject() {
return mJsonObject.isJsonObject();
}
public boolean isJsonPrimitive() {
return mJsonObject.isJsonPrimitive();
}
public boolean isJsonNull() {
return mJsonObject.isJsonNull();
}
public JsonObject getAsJsonObject() {
return mJsonObject.getAsJsonObject();
}
public JsonArray getAsJsonArray() {
return mJsonObject.getAsJsonArray();
}
public JsonPrimitive getAsJsonPrimitive() {
return mJsonObject.getAsJsonPrimitive();
}
public JsonNull getAsJsonNull() {
return mJsonObject.getAsJsonNull();
}
public boolean getAsBoolean() {
return mJsonObject.getAsBoolean();
}
public Number getAsNumber() {
return mJsonObject.getAsNumber();
}
public String getAsString() {
return mJsonObject.getAsString();
}
public double getAsDouble() {
return mJsonObject.getAsDouble();
}
public float getAsFloat() {
return mJsonObject.getAsFloat();
}
public long getAsLong() {
return mJsonObject.getAsLong();
}
public int getAsInt() {
return mJsonObject.getAsInt();
}
public byte getAsByte() {
return mJsonObject.getAsByte();
}
public char getAsCharacter() {
return mJsonObject.getAsCharacter();
}
public BigDecimal getAsBigDecimal() {
return mJsonObject.getAsBigDecimal();
}
public BigInteger getAsBigInteger() {
return mJsonObject.getAsBigInteger();
}
public short getAsShort() {
return mJsonObject.getAsShort();
}
}

View File

@@ -0,0 +1,32 @@
package org.autojs.autojs.tool;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
public class ThreadTool {
public static boolean wait(Supplier<Boolean> condition, int timeout) throws InterruptedException {
AtomicBoolean result = new AtomicBoolean(false);
Thread thread = new Thread(() -> {
while (!condition.get()) {
try {
//noinspection BusyWait
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
result.set(true);
});
thread.start();
thread.join(timeout);
if (thread.isAlive()) {
thread.interrupt();
}
return result.get();
}
public static boolean wait(Supplier<Boolean> condition) throws InterruptedException {
return wait(condition, 10 * 1000);
}
}

View File

@@ -2,36 +2,36 @@ package org.autojs.autojs.tool;
import android.content.Context;
import android.net.DhcpInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.text.format.Formatter;
import android.util.Log;
import java.math.BigInteger;
import java.net.InetAddress;
import static android.content.Context.WIFI_SERVICE;
import java.net.UnknownHostException;
import java.nio.ByteOrder;
/**
* Created by Stardust on 2017/5/11.
*/
public class WifiTool {
public static String getWifiAddress(Context context) {
WifiManager wifiMgr = (WifiManager) context.getApplicationContext().getSystemService(WIFI_SERVICE);
if(wifiMgr == null){
return null;
public static String getRouterIp(Context context) {
byte[] ipAddressByte = getIpAddressByte(context);
try {
return InetAddress.getByAddress(ipAddressByte).getHostAddress();
} catch (UnknownHostException e) {
Log.e(WifiTool.class.getSimpleName(), "Error getting Hotspot IP address ", e);
return "0.0.0.0";
}
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
int ip = wifiInfo.getIpAddress();
return Formatter.formatIpAddress(ip);
}
public static String getRouterIp(Context context){
WifiManager wifiService = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if(wifiService == null){
return null;
private static byte[] getIpAddressByte(Context context) {
WifiManager manager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
DhcpInfo dhcp = manager.getDhcpInfo();
int ipAddress = dhcp.gateway;
if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) {
ipAddress = Integer.reverseBytes(ipAddress);
}
DhcpInfo dhcpInfo = wifiService.getDhcpInfo();
return Formatter.formatIpAddress(dhcpInfo.gateway);
return BigInteger.valueOf(ipAddress).toByteArray();
}
}

View File

@@ -155,15 +155,14 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp
mWindow.collapse();
if (!RootTool.isRootAvailable()) {
DialogUtils.showDialog(new NotAskAgainDialog.Builder(mContext, "CircularMenu.root")
.title(R.string.text_device_not_rooted)
.content(R.string.prompt_device_not_rooted)
.neutralText(R.string.text_device_rooted)
.positiveText(R.string.ok)
.onNeutral(((dialog, which) -> mRecorder.start()))
.title(R.string.text_no_root_access)
.content(R.string.no_root_access_for_record)
.negativeText(R.string.text_quit)
.positiveText(R.string.text_insist_on_record)
.onPositive(((dialog, which) -> mRecorder.start()))
.build());
} else {
mRecorder.start();
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.net.Uri;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import android.util.AttributeSet;
import android.webkit.ValueCallback;
@@ -96,7 +97,7 @@ public class CommunityWebView extends EWebView {
Scripts.INSTANCE.run(file);
}, error -> {
error.printStackTrace();
Snackbar.make(CommunityWebView.this, R.string.text_download_failed, Toast.LENGTH_SHORT).show();
Snackbar.make(CommunityWebView.this, R.string.text_download_failed, BaseTransientBottomBar.LENGTH_SHORT).show();
});
}

View File

@@ -37,6 +37,8 @@ import org.autojs.autojs.Pref;
import org.autojs.autojs.R;
import org.autojs.autojs.external.foreground.ForegroundService;
import org.autojs.autojs.pluginclient.DevPluginService;
import org.autojs.autojs.pluginclient.JsonSocketClient;
import org.autojs.autojs.pluginclient.JsonSocketServer;
import org.autojs.autojs.tool.AccessibilityServiceTool;
import org.autojs.autojs.tool.Observers;
import org.autojs.autojs.tool.RootTool;
@@ -49,6 +51,7 @@ import org.autojs.autojs.ui.settings.SettingsActivity;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@@ -58,10 +61,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/**
* Created by Stardust on Jan 30, 2017.
* Modified by SuperMonster003 on Nov 16, 2021.
* Modified by SuperMonster003 as of Nov 16, 2021.
*/
@SuppressLint("NonConstantResourceId")
@SuppressWarnings("ResultOfMethodCallIgnored")
@@ -79,6 +81,11 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
private final DrawerMenuItem mAccessibilityServiceItem = new DrawerMenuItem(R.drawable.ic_accessibility_black_48dp, R.string.text_accessibility_service, 0, this::enableOrDisableAccessibilityService);
private final DrawerMenuItem mForegroundServiceItem = new DrawerMenuItem(R.drawable.ic_service_green, R.string.text_foreground_service, R.string.key_foreground_service, this::toggleForegroundService);
private final DrawerMenuItem mFloatingWindowItem = new DrawerMenuItem(R.drawable.ic_robot_64, R.string.text_floating_window, 0, this::showOrDismissFloatingWindow);
private final DrawerMenuItem mClientModeItem = new DrawerMenuItem(R.drawable.ic_computer_black_48dp, R.string.text_client_mode, 0, this::toggleRemoteServerCxn);
private final DrawerMenuItem mServerModeItem = new DrawerMenuItem(R.drawable.ic_smartphone_black_48dp, R.string.text_server_mode, 0, this::toggleLocalServerCxn);
private final DrawerMenuItem mNotificationPermissionItem = new DrawerMenuItem(R.drawable.ic_ali_notification, R.string.text_notification_permission, 0, this::goToNotificationServiceSettings);
private final DrawerMenuItem mUsageStatsPermissionItem = new DrawerMenuItem(R.drawable.ic_assessment_black_48dp, R.string.text_usage_stats_permission, 0, this::goToUsageStatsSettings);
private final DrawerMenuItem mIgnoreBatteryOptimizationsItem = new DrawerMenuItem(R.drawable.ic_battery_std_black_48dp, R.string.text_ignore_battery_optimizations, 0, this::toggleIgnoreBatteryOptimizations);
@@ -86,36 +93,55 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
private final DrawerMenuItem mWriteSystemSettingsItem = new DrawerMenuItem(R.drawable.ic_settings_black_48dp, R.string.text_write_system_settings, 0, this::goToWriteSystemSettings);
private final DrawerMenuItem mWriteSecuritySettingsItem = new DrawerMenuItem(R.drawable.ic_security_black_48dp, R.string.text_write_secure_settings, 0, this::toggleWriteSecureSettings);
private final DrawerMenuItem mFloatingWindowItem = new DrawerMenuItem(R.drawable.ic_robot_64, R.string.text_floating_window, 0, this::showOrDismissFloatingWindow);
private final DrawerMenuItem mConnectionItem = new DrawerMenuItem(R.drawable.ic_computer_black_48dp, R.string.debug, 0, this::connectOrDisconnectToRemote);
private final DevPluginService devPlugin = DevPluginService.getInstance();
private DrawerMenuAdapter mDrawerMenuAdapter;
private Disposable mConnectionStateDisposable;
private Disposable mClientConnectionStateDisposable;
private Disposable mServerConnectionStateDisposable;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mConnectionStateDisposable = DevPluginService.getInstance().connectionState()
mClientConnectionStateDisposable = JsonSocketClient.cxnState
.observeOn(AndroidSchedulers.mainThread())
.subscribe(state -> {
setChecked(mConnectionItem, state.getState() == DevPluginService.State.CONNECTED);
setProgress(mConnectionItem, state.getState() == DevPluginService.State.CONNECTING);
if (state.getException() != null) {
showMessage(state.getException().getMessage());
}
});
.subscribe(state -> setItemState(mClientModeItem, state));
mServerConnectionStateDisposable = JsonSocketServer.cxnState
.observeOn(AndroidSchedulers.mainThread())
.subscribe(state -> setItemState(mServerModeItem, state));
EventBus.getDefault().register(this);
}
@Override
public void onResume() {
super.onResume();
syncSwitchState();
}
@Override
public void onDestroy() {
super.onDestroy();
mClientConnectionStateDisposable.dispose();
mServerConnectionStateDisposable.dispose();
EventBus.getDefault().unregister(this);
}
@AfterViews
void setUpViews() {
public void setUpViews() {
ThemeColorManager.addViewBackground(mHeaderView);
initMenuItems();
if (Pref.isFloatingMenuShown()) {
FloatyWindowManger.showCircularMenuIfNeeded();
setChecked(mFloatingWindowItem, true);
}
setChecked(mConnectionItem, DevPluginService.getInstance().isConnected());
JsonSocketClient jsonSocketClient = devPlugin.getJsonSocketClient();
if (jsonSocketClient != null) {
setChecked(mClientModeItem, jsonSocketClient.isConnected());
}
if (Pref.isForegroundServiceEnabled()) {
ForegroundService.start(GlobalAppContext.get());
setChecked(mForegroundServiceItem, true);
@@ -128,6 +154,13 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
mAccessibilityServiceItem,
mForegroundServiceItem,
new DrawerMenuGroup(R.string.text_tools),
mFloatingWindowItem,
new DrawerMenuGroup(R.string.text_connect_to_pc),
mClientModeItem,
mServerModeItem,
new DrawerMenuGroup(R.string.text_permission),
mNotificationPermissionItem,
mUsageStatsPermissionItem,
@@ -136,10 +169,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
mWriteSystemSettingsItem,
mWriteSecuritySettingsItem,
new DrawerMenuGroup(R.string.text_tools),
mFloatingWindowItem,
mConnectionItem,
new DrawerMenuGroup(R.string.text_appearance),
new DrawerMenuItem(R.drawable.ic_night_mode, R.string.text_night_mode, R.string.key_night_mode, this::toggleNightMode),
new DrawerMenuItem(R.drawable.ic_personalize, R.string.text_theme_color, this::openThemeColorSettings)
@@ -148,20 +177,17 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
mDrawerMenu.setLayoutManager(new LinearLayoutManager(getContext()));
}
void enableOrDisableAccessibilityService(DrawerMenuItemViewHolder holder) {
public void enableOrDisableAccessibilityService(DrawerMenuItemViewHolder holder) {
boolean isAccessibilityServiceEnabled = isAccessibilityServiceEnabled();
boolean checked = holder.getSwitchCompat().isChecked();
if (checked && !isAccessibilityServiceEnabled) {
enableAccessibilityService();
} else if (!checked && isAccessibilityServiceEnabled) {
if (!AccessibilityService.Companion.disable()) {
AccessibilityServiceTool.goToAccessibilitySetting();
}
disableAccessibilityService();
}
}
void goToNotificationServiceSettings(DrawerMenuItemViewHolder holder) {
public void goToNotificationServiceSettings(DrawerMenuItemViewHolder holder) {
boolean enabled = NotificationListenerService.Companion.getInstance() != null;
boolean checked = holder.getSwitchCompat().isChecked();
if ((checked && !enabled) || (!checked && enabled)) {
@@ -169,7 +195,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
}
}
void goToUsageStatsSettings(DrawerMenuItemViewHolder holder) {
public void goToUsageStatsSettings(DrawerMenuItemViewHolder holder) {
Context context = getContext();
boolean enabled = false;
if (context != null) {
@@ -177,27 +203,28 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
}
boolean checked = holder.getSwitchCompat().isChecked();
if (checked && !enabled) {
if (new NotAskAgainDialog.Builder(context, "DrawerFragment.usage_stats")
.title(R.string.text_usage_stats_permission)
.content(R.string.description_usage_stats_permission)
.positiveText(R.string.ok)
.dismissListener(dialog -> {
if (context != null) {
IntentUtil.requestAppUsagePermission(context);
}
})
.show() == null) {
if (context != null) {
IntentUtil.requestAppUsagePermission(context);
}
if (getUsageStatsDialog(context) == null) {
syncSwitchState();
}
}
if (!checked && enabled) {
} else if (!checked && enabled) {
IntentUtil.requestAppUsagePermission(context);
}
}
void showOrDismissFloatingWindow(DrawerMenuItemViewHolder holder) {
private MaterialDialog getUsageStatsDialog(Context context) {
return new NotAskAgainDialog.Builder(context, "DrawerFragment.usage_stats")
.title(R.string.text_usage_stats_permission)
.content(R.string.description_usage_stats_permission)
.positiveText(R.string.ok)
.dismissListener(dialog -> {
if (context != null) {
IntentUtil.requestAppUsagePermission(context);
}
})
.show();
}
public void showOrDismissFloatingWindow(DrawerMenuItemViewHolder holder) {
boolean isFloatingWindowShowing = FloatyWindowManger.isCircularMenuShowing();
boolean checked = holder.getSwitchCompat().isChecked();
if (getActivity() != null && !getActivity().isFinishing()) {
@@ -212,7 +239,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
}
@SuppressLint("BatteryLife")
void toggleIgnoreBatteryOptimizations(DrawerMenuItemViewHolder holder) {
public void toggleIgnoreBatteryOptimizations(DrawerMenuItemViewHolder holder) {
Context context = getContext();
try {
Intent intent = new Intent();
@@ -236,15 +263,15 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
}
}
void openThemeColorSettings(DrawerMenuItemViewHolder holder) {
public void openThemeColorSettings(DrawerMenuItemViewHolder holder) {
SettingsActivity.selectThemeColor(getActivity());
}
void toggleNightMode(DrawerMenuItemViewHolder holder) {
public void toggleNightMode(DrawerMenuItemViewHolder holder) {
((BaseActivity) requireActivity()).setNightModeEnabled(holder.getSwitchCompat().isChecked());
}
void goToDisplayOverOtherAppsSettings(DrawerMenuItemViewHolder holder) {
public void goToDisplayOverOtherAppsSettings(DrawerMenuItemViewHolder holder) {
boolean checked = holder.getSwitchCompat().isChecked();
Context context = getContext();
if (checked != FloatingPermission.canDrawOverlays(context)) {
@@ -252,7 +279,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
}
}
void goToWriteSystemSettings(DrawerMenuItemViewHolder holder) {
public void goToWriteSystemSettings(DrawerMenuItemViewHolder holder) {
boolean checked = holder.getSwitchCompat().isChecked();
if (checked != Settings.System.canWrite(getContext())) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
@@ -273,6 +300,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
private boolean hasWriteSecureSettingsAccess() {
Context context = getContext();
if (context != null) {
@SuppressLint("WrongConstant")
int checkVal = context.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS_PERMISSION);
return checkVal == PackageManager.PERMISSION_GRANTED;
}
@@ -331,7 +359,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
if (context == null) {
return;
}
final int SNACKBAR_DURATION = 1000;
final int SNACK_BAR_DURATION = 1000;
String scriptAction = state ? "grant" : "revoke";
String script = "adb shell pm " + scriptAction + " " + context.getPackageName() + " " + WRITE_SECURE_SETTINGS_PERMISSION;
@@ -343,7 +371,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
View view = dialog.getView();
int resultRes = hasWriteSecureSettingsAccess() ? R.string.text_granted : R.string.text_not_granted;
if (view != null) {
Snackbar.make(view, resultRes, SNACKBAR_DURATION).show();
Snackbar.make(view, resultRes, SNACK_BAR_DURATION).show();
} else {
Toast.makeText(context, resultRes, Toast.LENGTH_SHORT).show();
}
@@ -356,7 +384,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
View view = dialog.getView();
int textRes = R.string.text_command_already_copied_to_clip;
if (view != null) {
Snackbar.make(view, textRes, SNACKBAR_DURATION).show();
Snackbar.make(view, textRes, SNACK_BAR_DURATION).show();
} else {
Toast.makeText(context, textRes, Toast.LENGTH_SHORT).show();
}
@@ -373,23 +401,43 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
Observable.fromCallable(() -> Pref.shouldEnableAccessibilityServiceByRoot() && !isAccessibilityServiceEnabled())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(needed -> {
if (needed) {
if (needed && RootTool.isRootAvailable()) {
enableAccessibilityServiceByRoot();
}
});
}
void connectOrDisconnectToRemote(DrawerMenuItemViewHolder holder) {
private void toggleRemoteServerCxn(DrawerMenuItemViewHolder holder) throws IOException {
JsonSocketClient jsonSocketClient = devPlugin.getJsonSocketClient();
boolean disconnected = jsonSocketClient == null || !jsonSocketClient.isSocketReady();
boolean checked = holder.getSwitchCompat().isChecked();
boolean connected = DevPluginService.getInstance().isConnected();
if (checked && !connected) {
inputRemoteHost();
} else if (!checked && connected) {
DevPluginService.getInstance().disconnectIfNeeded();
if (checked) {
if (disconnected) {
inputRemoteHost();
}
} else {
if (jsonSocketClient != null) {
jsonSocketClient.switchOff();
}
}
}
@SuppressLint("CheckResult")
private void toggleLocalServerCxn(DrawerMenuItemViewHolder holder) throws IOException {
JsonSocketServer jsonSocketServer = devPlugin.getJsonSocketServer();
boolean checked = holder.getSwitchCompat().isChecked();
if (checked) {
devPlugin.enableLocalServer()
.subscribe(Observers.emptyConsumer(), this::onAJServerConnectException);
} else {
if (jsonSocketServer != null) {
jsonSocketServer.switchOff();
}
}
}
private void toggleForegroundService(DrawerMenuItemViewHolder holder) {
boolean checked = holder.getSwitchCompat().isChecked();
@@ -400,37 +448,46 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
}
}
@SuppressLint("CheckResult")
private void inputRemoteHost() {
Context activity = getActivity();
String host = Pref.getServerAddressOrDefault(WifiTool.getRouterIp(Objects.requireNonNull(activity)));
new MaterialDialog.Builder(activity)
.title(R.string.text_server_address)
.input("", host, (dialog, input) -> {
.title(R.string.text_pc_server_address)
.input(getInputHint(), host, (dialog, input) -> {
Pref.saveServerAddress(input.toString());
DevPluginService.getInstance().connectToServer(input.toString())
.subscribe(Observers.emptyConsumer(), this::onConnectException);
devPlugin.connectToRemoteServer(input.toString())
.subscribe(Observers.emptyConsumer(), this::onPCServerConnectException);
})
.neutralText(R.string.text_help)
.onNeutral((dialog, which) -> {
setChecked(mConnectionItem, false);
setChecked(mClientModeItem, false);
IntentUtil.browse(activity, URL_DEV_PLUGIN);
})
.cancelListener(dialog -> setChecked(mConnectionItem, false))
.cancelListener(dialog -> setChecked(mClientModeItem, false))
.show();
}
private void onConnectException(Throwable e) {
setChecked(mConnectionItem, false);
Toast.makeText(GlobalAppContext.get(), getString(R.string.error_connect_to_remote, e.getMessage()),
private String getInputHint() {
Context context = getContext();
if (context != null) {
return context.getString(R.string.text_pc_server_address);
}
return "Input a server address";
}
private void onPCServerConnectException(Throwable e) {
setChecked(mClientModeItem, false);
Toast.makeText(getContext(),
getString(R.string.error_connect_to_remote, e.getMessage()),
Toast.LENGTH_LONG).show();
}
@Override
public void onResume() {
super.onResume();
syncSwitchState();
private void onAJServerConnectException(Throwable e) {
setChecked(mServerModeItem, false);
Toast.makeText(getContext(),
getString(R.string.error_enable_server, e.getMessage()),
Toast.LENGTH_LONG).show();
}
private void syncSwitchState() {
@@ -456,12 +513,22 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
setChecked(mWriteSecuritySettingsItem, hasWriteSecureSettingsAccess());
}
private boolean isAccessibilityServiceEnabled() {
return AccessibilityServiceTool.isAccessibilityServiceEnabled(getActivity());
}
private void enableAccessibilityService() {
if (!Pref.shouldEnableAccessibilityServiceByRoot()) {
if (Pref.shouldEnableAccessibilityServiceByRoot() && RootTool.isRootAvailable()) {
enableAccessibilityServiceByRoot();
} else {
AccessibilityServiceTool.goToAccessibilitySetting();
}
}
private void disableAccessibilityService() {
if (!AccessibilityService.Companion.disable()) {
AccessibilityServiceTool.goToAccessibilitySetting();
return;
}
enableAccessibilityServiceByRoot();
}
@SuppressLint("CheckResult")
@@ -479,27 +546,18 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
});
}
@SuppressWarnings("unused")
@Subscribe
public void onCircularMenuStateChange(CircularMenu.StateChangeEvent event) {
setChecked(mFloatingWindowItem, event.getCurrentState() != CircularMenu.STATE_CLOSED);
}
@Override
public void onDestroy() {
super.onDestroy();
mConnectionStateDisposable.dispose();
EventBus.getDefault().unregister(this);
}
private void showMessage(CharSequence text) {
if (getContext() == null)
return;
Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show();
}
private void setProgress(DrawerMenuItem item, boolean progress) {
item.setProgress(progress);
mDrawerMenuAdapter.notifyItemChanged(item);
@@ -510,7 +568,11 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
mDrawerMenuAdapter.notifyItemChanged(item);
}
private boolean isAccessibilityServiceEnabled() {
return AccessibilityServiceTool.isAccessibilityServiceEnabled(getActivity());
private void setItemState(DrawerMenuItem item, DevPluginService.State state) {
setChecked(item, state.getState() == DevPluginService.State.CONNECTED);
setProgress(item, state.getState() == DevPluginService.State.CONNECTING);
if (state.getException() != null) {
showMessage(state.getException().getMessage());
}
}
}

View File

@@ -1,5 +1,7 @@
package org.autojs.autojs.ui.main.drawer;
import java.io.IOException;
/**
* Created by Stardust on 2017/8/25.
*/
@@ -7,17 +9,17 @@ public class DrawerMenuItem {
public interface Action {
void onClick(DrawerMenuItemViewHolder holder);
void onClick(DrawerMenuItemViewHolder holder) throws IOException;
}
private int mIcon;
private int mTitle;
private final int mIcon;
private final int mTitle;
private final Action mAction;
private boolean mAntiShake;
private boolean mSwitchEnabled;
private int mPrefKey;
private Action mAction;
private boolean mSwitchChecked;
private boolean mOnProgress;
private boolean mSwitchEnabled;
private boolean mSwitchChecked;
private int mPrefKey;
private int mNotificationCount;
public DrawerMenuItem(int icon, int title, Action action) {
@@ -81,7 +83,7 @@ public class DrawerMenuItem {
return mPrefKey;
}
public void performAction(DrawerMenuItemViewHolder holder) {
public void performAction(DrawerMenuItemViewHolder holder) throws IOException {
if (mAction != null)
mAction.onClick(holder);
}

View File

@@ -1,24 +1,23 @@
package org.autojs.autojs.ui.main.drawer;
import android.content.pm.PackageManager;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.autojs.autojs.R;
import org.autojs.autojs.ui.widget.BindableViewHolder;
import org.autojs.autojs.ui.widget.PrefSwitch;
import org.autojs.autojs.ui.widget.SwitchCompat;
import java.io.IOException;
import butterknife.BindView;
import butterknife.ButterKnife;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
/**
* Created by Stardust on 2017/12/10.
*/
@@ -48,12 +47,22 @@ public class DrawerMenuItemViewHolder extends BindableViewHolder<DrawerMenuItem>
public DrawerMenuItemViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
mSwitchCompat.setOnCheckedChangeListener((buttonView, isChecked) -> onClick());
mSwitchCompat.setOnCheckedChangeListener((buttonView, isChecked) -> {
try {
onClick();
} catch (IOException e) {
e.printStackTrace();
}
});
itemView.setOnClickListener(v -> {
if (mSwitchCompat.getVisibility() == VISIBLE) {
mSwitchCompat.toggle();
} else {
onClick();
try {
onClick();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
@@ -93,7 +102,7 @@ public class DrawerMenuItemViewHolder extends BindableViewHolder<DrawerMenuItem>
}
}
private void onClick() {
private void onClick() throws IOException {
mDrawerMenuItem.setChecked(mSwitchCompat.isChecked());
if (mAntiShake && (System.currentTimeMillis() - mLastClickMillis < CLICK_TIMEOUT)) {
// Toast.makeText(itemView.getContext(), R.string.text_click_too_frequently, Toast.LENGTH_SHORT).show();

View File

@@ -51,7 +51,7 @@
<string name="text_floating_window">Floating Window</string>
<string name="text_error_report">Bug Report</string>
<string name="text_press_again_to_exit">Press again to exit</string>
<string name="text_already_stop_n_scripts">%d script(s) is(are) stopped</string>
<string name="text_already_stop_n_scripts">%d script(s) stopped</string>
<string name="text_start_running">Running</string>
<string name="text_open_by_other_apps">Open by other apps</string>
<string name="text_rename">Rename</string>
@@ -62,7 +62,7 @@
<string name="text_error">Error</string>
<string name="text_copy_debug_info">Copy debugging log</string>
<string name="text_it_is_the_developer_of_app">This is a software developer(。・・)</string>
<string name="text_please_choose">Please choose </string>
<string name="text_please_choose">Please choose</string>
<string name="text_crash">Crash~ o(≧□≦)o</string>
<string name="crash_feedback">(ಥ _ ಥ)Exit or submit a bug report or copy debugging log to the developer(≧∇≦)ノ</string>
<string name="text_clear">Clear</string>
@@ -170,18 +170,22 @@
<string name="summary_guard_mode">Prevent automation of scripts when Auto.js in the front</string>
<string name="text_layout_inspector_is_dumping" tools:ignore="TypographyEllipsis">Inspecting layout...</string>
<string name="text_force_stop">Force stop</string>
<string name="text_execution_finished" formatted="false">\\n------------\\n[%s]Finishedspent %f seconds.</string>
<string name="text_execution_finished" formatted="false">[%s] finished in %s seconds.\n</string>
<string name="text_about_me_and_repo">About app and developer</string>
<string name="text_attribute">Attribute</string>
<string name="text_value">Value</string>
<string name="text_show_widget_information">View info</string>
<string name="text_show_layout_hierarchy">View in layout bounds\' view</string>
<string name="default_value_script_dir_path">/Scripts/</string>
<string name="debug">Connect to PC</string>
<string name="text_connect_to_pc">Connect to PC</string>
<string name="text_client_mode">Client mode</string>
<string name="text_server_mode">Server mode</string>
<string name="text_pc_server_address">PC server address</string>
<string name="text_night_mode">Dark mode</string>
<string name="text_stable_mode">Stable mode</string>
<string name="text_foreground_service">Foreground service</string>
<string name="text_usage_stats_permission">Usage stats access</string>
<string name="description_usage_stats_permission">Provides access to device usage history and statistics which results in currentPackage() a more accurate result</string>
<string name="text_volume_down_control">Volume down control</string>
<string name="text_task">Task</string>
<string name="text_next_run_time">Next</string>
@@ -238,6 +242,7 @@
<string name="text_time">Time</string>
<string name="text_size">Size</string>
<string name="text_type">Type</string>
<string name="text_icon">Icon</string>
<string name="text_js_file">*.js file</string>
<string name="text_notification_permission">Notification access</string>
<string name="text_permission">Permission</string>
@@ -253,4 +258,10 @@
<string name="text_failed">Failed</string>
<string name="mt_color_picker_title">Color picker</string>
<string name="mt_custom">Custom</string>
<string name="text_no_root_access">No root access</string>
<string name="no_root_access_for_record">Auto.js has no root access to record a script. Continue?</string>
<string name="text_insist_on_record">Record</string>
<string name="text_quit">Quit</string>
<string name="error_connect_to_remote">Can\'t connect to the remote server: %s</string>
<string name="error_enable_server">Can\'t enable the AutoJs6 server: %s</string>
</resources>

View File

@@ -51,7 +51,7 @@
<string name="text_floating_window">悬浮窗</string>
<string name="text_error_report">错误报告</string>
<string name="text_press_again_to_exit">再按一次退出程序</string>
<string name="text_already_stop_n_scripts">已停止%d个正在运行的脚本</string>
<string name="text_already_stop_n_scripts">已停止 %d 个正在运行的脚本</string>
<string name="text_start_running">开始运行</string>
<string name="text_open_by_other_apps">用其他应用打开</string>
<string name="text_rename">重命名</string>
@@ -142,13 +142,15 @@
<string name="key_guard_mode" translatable="false">key_guard_mode</string>
<string name="text_layout_inspector_is_dumping">布局分析中</string>
<string name="text_force_stop">强制停止</string>
<string name="text_execution_finished" formatted="false">\n------------\n[%s]运行结束,用时%f秒</string>
<string name="text_execution_finished" formatted="false">[%s] 运行结束 (用时 %s 秒)\n</string>
<string name="text_again"></string>
<string name="text_again_and_again">又双</string>
<string name="text_again_and_again_again">又双叒</string>
<string name="text_again_and_again_again_again">又双叒叕</string>
<string name="debug">连接电脑</string>
<string name="text_server_address">服务器地址</string>
<string name="text_connect_to_pc">连接到计算机</string>
<string name="text_client_mode">客户端模式</string>
<string name="text_server_mode">服务端模式</string>
<string name="text_pc_server_address">PC 服务端地址</string>
<string name="text_about_me_and_repo">关于项目与开发者</string>
<string name="text_attribute">属性</string>
<string name="text_value"></string>
@@ -311,9 +313,9 @@
<string name="format_default_package_name" formatted="true">com.example.script%d</string>
<string name="text_should_not_be_empty">不能为空</string>
<string name="text_icon">图标</string>
<string name="text_device_not_rooted">设备没有root</string>
<string name="prompt_device_not_rooted">监测到您的设备没有root, 录制脚本需root权限, 是否继续?</string>
<string name="text_device_rooted">仍要录制</string>
<string name="text_no_root_access">root权限</string>
<string name="no_root_access_for_record">Auto.js无root权限, 录制脚本需root权限, 是否继续录制</string>
<string name="text_insist_on_record">录制</string>
<string name="nodebb_error_invalid_login_credentials">无效的登录凭证</string>
<string name="nodebb_error_change_password_error_length">密码太短</string>
<string name="nodebb_error_email_taken">邮箱已被占用</string>
@@ -357,6 +359,7 @@
<string name="text_close">关闭</string>
<string name="text_execute">执行</string>
<string name="error_connect_to_remote">连接失败: %s</string>
<string name="error_enable_server">AutoJs6 服务启用失败: %s</string>
<string name="text_are_you_sure_to_delete">确定要删除%s吗</string>
<string name="text_run_on_broadcast">广播触发任务</string>
<string name="text_search_java_class">搜索Java包/类</string>
@@ -393,7 +396,7 @@
<string name="text_run_on_package_update">应用更新时</string>
<string name="text_run_on_headset_plug">耳机插拔时</string>
<string name="text_run_on_time_tick">每分钟一次</string>
<string name="text_run_on_config_change">某些设置(屏幕方向,地区等)更改时</string>
<string name="text_run_on_config_change">某些设置 (屏幕方向, 地区等) 更改时</string>
<string name="error_pattern_syntax">正则表达式错误</string>
<string name="text_invalid_package_name">非法包名</string>
<string name="error_cannot_rename">重命名失败</string>
@@ -403,7 +406,7 @@
<string name="key_night_mode">key_night_mode</string>
<string name="text_run_on_startup">Auto.js启动时</string>
<string name="text_usage_stats_permission">查看使用情况权限</string>
<string name="description_usage_stats_permission">通过\"查看使用情况\"权限可获取本设备过去使用应用的情况从而使获取当前应用(currentPackage)更准确</string>
<string name="description_usage_stats_permission">通过\"查看使用情况\"权限可获取本设备过去使用应用的情况, 从而使\"获取当前应用\" (currentPackage) 结果更准确</string>
<string name="text_open_with">打开方式</string>
<string name="text_root">Root</string>
<string name="text_no_root">免Root</string>
@@ -421,4 +424,5 @@
<string name="text_failed">失败</string>
<string name="mt_color_picker_title">选择颜色</string>
<string name="mt_custom">自定义</string>
<string name="text_quit">放弃</string>
</resources>

View File

@@ -58,8 +58,8 @@
<string name="text_press_again_to_exit">Press again to exit</string>
<string name="text_common_function">常用函数</string>
<string name="sorry_for_crash">很抱歉(ಥ _ ಥ)程序遇到未知错误,即将停止运行\n错误代码</string>
<string name="text_no_running_script">No running script</string>
<string name="text_already_stop_n_scripts">已停止%d个正在运行的脚本</string>
<string name="text_no_running_scripts">No running script</string>
<string name="text_already_stop_n_scripts">已停止 %d 个正在运行的脚本</string>
<string name="text_start_running">Running</string>
<string name="text_open_by_other_apps">Open by other apps</string>
<string name="text_rename">Rename</string>
@@ -82,7 +82,7 @@
<string name="text_assist_clip">点击定位剪贴板</string>
<string name="text_script_stopped">Script stopped</string>
<string name="assist_mode_notice">(不会翻译)\"点击区域辅助服务\"是在要点击的区域不是文本时使用的,参见\"语法与API\"。\"点击区域辅助服务\"只有在\"自动操作服务\"开启时才有效。</string>
<string name="text_please_choose">Please choose</string>
<string name="text_please_choose">请选择</string>
<string name="text_crash">崩溃了o(≧□≦)o</string>
<string name="crash_feedback">(不会翻译)点击\"退出\"退出程序(ಥ _ ಥ)或者提交错误报告或者复制调试信息反馈给开发者(≧∇≦)ノ</string>
<string name="text_cannot_create_dialog_when_app_invisible">无法在后台显示弹框,脚本已停止运行</string>

View File

@@ -1,5 +1,7 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
plugins {
id "com.android.library"
id "kotlin-android"
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
@@ -7,9 +9,9 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
}
}
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:deprecation' << '-Xlint:unchecked'
}
// tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked"
// }
android {
compileSdkVersion versions.compile
@@ -22,7 +24,7 @@ android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
compileOptions {
@@ -39,37 +41,22 @@ repositories {
}
dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation "junit:junit:${junitVer}"
api 'org.greenrobot:eventbus:3.2.0'
implementation "net.lingala.zip4j:zip4j:2.9.1"
api 'net.lingala.zip4j:zip4j:2.9.1'
api('com.afollestad.material-dialogs:core:0.9.6.0')
api 'com.google.android.material:material:1.5.0-beta01'
api 'com.github.hyb1996:EnhancedFloaty:0.31'
api 'com.makeramen:roundedimageview:2.3.0'
implementation "com.afollestad.material-dialogs:core:0.9.6.0"
implementation "com.google.android.material:material:1.6.0-alpha01"
// OkHttp
api 'com.squareup.okhttp3:okhttp:5.0.0-alpha.3'
// JDeferred
api 'org.jdeferred:jdeferred-android-aar:1.2.6'
// RootShell
api 'com.github.Stericson:RootShell:1.6'
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.3"
// Gson
api 'com.google.code.gson:gson:2.8.9'
implementation "com.google.code.gson:gson:2.8.9"
// Log4j
api group: 'de.mindpipe.android', name: 'android-logging-log4j', version: '1.0.3'
api group: 'log4j', name: 'log4j', version: '1.2.17'
// Preference
api 'androidx.preference:preference-ktx:1.1.1'
implementation group: "de.mindpipe.android", name: "android-logging-log4j", version: "1.0.3"
implementation group: "log4j", name: "log4j", version: "1.2.17"
// Terminal emulator
api project(":libs:jackpal.androidterm.libtermexec-1.0")
@@ -78,8 +65,8 @@ dependencies {
api project(":libs:com.android.dx-1.7.0")
api project(":libs:org.mozilla.rhino-1.7.14")
api project(':libs:org.opencv-4.5.4')
api project(":libs:org.opencv-4.5.4")
api project(':common')
api project(':automator')
api project(":common")
api project(":automator")
}

View File

@@ -2,6 +2,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.stardust.autojs">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />

View File

@@ -1,37 +1,31 @@
var global = this;
let global = this;
runtime.init();
(function () {
//重定向importClass使其支持字符串参数
global.importClass =
(function () {
var __importClass__ = importClass;
return function (pack) {
if (typeof (pack) == "string") {
__importClass__(Packages[pack]);
} else {
__importClass__(pack);
}
}
})();
!function () {
// 重定向 importClass 使其支持字符串参数
global.importClass = (function () {
let __importClass__ = importClass;
return function (pack) {
__importClass__(typeof pack === 'string' ? Packages[pack] : pack);
};
})();
//内部函数
global.__asGlobal__ = function (obj, functions) {
var len = functions.length;
for (var i = 0; i < len; i++) {
var funcName = functions[i];
var func = obj[funcName]
if (!func) {
continue;
var func = obj[funcName];
if (func) {
(function (obj, funcName, func) {
global[funcName] = function () {
return func.apply(obj, arguments);
};
})(obj, funcName, func);
}
(function (obj, funcName, func) {
global[funcName] = function () {
return func.apply(obj, arguments);
};
})(obj, funcName, func);
}
}
};
global.__exitIfError__ = function (action, defReturnValue) {
try {
@@ -40,7 +34,7 @@ runtime.init();
if (err instanceof java.lang.Throwable) {
exit(err);
} else if (err instanceof Error) {
exit(new org.mozilla.javascript.EvaluatorException(err.name + ": " + err.message, err.fileName, err.lineNumber));
exit(new org.mozilla.javascript.EvaluatorException(err.name + ': ' + err.message, err.fileName, err.lineNumber));
} else {
exit();
}
@@ -48,25 +42,28 @@ runtime.init();
}
};
// 初始化基础模块
global.timers = require('__timers__.js')(runtime, global);
// 初始化基础模块
global.timers = require('__timers__.js')(runtime, global);
//初始化不依赖环境的模块
global.JSON = require('json2.js');
global.util = require('__util__.js');
global.device = runtime.device;
global.Promise = require('promise.js');
//设置JavaScriptBridges用于与Java层的交互和数据转换
runtime.bridges.setBridges(require('__bridges__.js'));
//初始化不依赖环境的模块
global.JSON = require('json2.js');
global.util = require('__util__.js');
global.device = runtime.device;
global.Promise = require('promise.js');
//设置JavaScriptBridges用于与Java层的交互和数据转换
runtime.bridges.setBridges(require('__bridges__.js'));
//初始化全局函数
require("__globals__")(runtime, global);
require('__globals__')(runtime, global);
require('custom-polyfill').fill();
//初始化一般模块
(function (scope) {
var modules = ['app', 'automator', 'console', 'dialogs', 'io', 'selector', 'shell', 'web', 'ui',
"images", "threads", "events", "engines", "RootAutomator", "http", "storages", "floaty",
"sensors", "media", "plugins", "continuation"];
'images', 'threads', 'events', 'engines', 'RootAutomator', 'http', 'storages', 'floaty',
'sensors', 'media', 'plugins', 'continuation', '$base64'];
var len = modules.length;
for (var i = 0; i < len; i++) {
var m = modules[i];
@@ -74,17 +71,19 @@ runtime.init();
}
})(global);
importClass(android.view.KeyEvent);
importClass(com.stardust.autojs.core.util.Shell);
importClass(android.graphics.Paint);
KeyEvent = android.view.KeyEvent;
Shell = com.stardust.autojs.core.util.Shell;
Paint = android.graphics.Paint;
Canvas = com.stardust.autojs.core.graphics.ScriptCanvas;
Image = com.stardust.autojs.core.image.ImageWrapper;
//重定向require以便支持相对路径和npm模块
Module = require("jvm-npm.js");
OkHttpClient = Packages.okhttp3.OkHttpClient;
MutableOkHttp = com.stardust.autojs.core.http.MutableOkHttp;
Intent = android.content.Intent;
BroadcastReceiver = com.stardust.autojs.core.content.BroadcastReceiver;
// 重定向require以便支持相对路径和npm模块
Module = require('jvm-npm.js');
require = Module.require;
})();
}();

View File

@@ -0,0 +1,27 @@
module.exports = function (runtime, global) {
const Base64 = android.util.Base64;
const $base64 = () => void 0;
let _ = {
charsetNames: [
// charset names updated up to android sdk 27
'us-ascii', 'iso-8859-1', 'utf-8', 'utf-16be', 'utf-16le', 'utf-16',
],
isValidEncoding(encode) {
return this.charsetNames.includes(encode);
},
};
// noinspection JSValidateTypes
$base64.encode = (str, encoding) => _.isValidEncoding(encoding)
? Base64.encodeToString(new java.lang.String(str).getBytes(encoding), Base64.NO_WRAP)
: Base64.encodeToString(new java.lang.String(str).getBytes(), Base64.NO_WRAP);
// noinspection JSValidateTypes
$base64.decode = (str, encoding) => _.isValidEncoding(encoding)
? String(new java.lang.String(Base64.decode(str, Base64.NO_WRAP), encoding))
: String(new java.lang.String(Base64.decode(str, Base64.NO_WRAP)));
return $base64;
};

View File

@@ -1,145 +1,266 @@
let Looper = android.os.Looper;
let Toast = android.widget.Toast;
let Runnable = java.lang.Runnable;
module.exports = function(runtime, global){
global.toast = function(text){
runtime.toast(text);
}
global.toastLog = function(text){
runtime.toast(text);
global.log(text);
}
global.sleep = function(t) {
if(ui.isUiThread()){
throw new Error("不能在ui线程执行阻塞操作请使用setTimeout代替");
}
runtime.sleep(t);
}
global.isStopped = function(){
return runtime.isStopped();
}
global.isShuttingDown = global.isShopped;
global.notStopped = function(){
return !isStopped();
}
global.isRunning = global.notStopped;
global.exit = runtime.exit.bind(runtime);
global.stop = global.exit;
global.setClip = function(text){
runtime.setClip(text);
}
global.getClip = function(text){
return runtime.getClip();
}
global.currentPackage = function(){
global.auto();
return runtime.info.getLatestPackage();
}
global.currentActivity = function(){
global.auto();
return runtime.info.getLatestActivity();
}
global.waitForActivity = function(activity, period){
ensureNonUiThread();
period = period || 200;
while(global.currentActivity() != activity){
sleep(period);
}
}
global.waitForPackage = function(packageName, period){
ensureNonUiThread();
period = period || 200;
while(global.currentPackage() != packageName){
sleep(period);
}
}
function ensureNonUiThread() {
if(ui.isUiThread()){
throw new Error("不能在ui线程执行阻塞操作请在子线程或子脚本执行或者使用setInterval循环检测当前activity和package");
}
}
global.random = function(min, max){
if(arguments.length == 0){
return Math.random();
}
return Math.floor(Math.random() * (max - min + 1)) + min;
}
global.setScreenMetrics = runtime.setScreenMetrics.bind(runtime);
global.requiresApi = runtime.requiresApi.bind(runtime);
global.requiresAutojsVersion = function(version){
if(typeof(version) == 'number'){
if(compare(version, app.autojs.versionCode) > 0){
throw new Error("需要Auto.js版本号" + version + "以上才能运行");
module.exports = function (runtime, global) {
let _ = {
buildTypes: {
release: 100, beta: 50, alpha: 0,
},
ensureNonUiThread() {
if (ui.isUiThread()) {
throw new Error('不能在ui线程执行阻塞操作请在子线程或子脚本执行或者使用setInterval循环检测当前activity和package');
}
}else{
if(compareVersion(version, app.autojs.versionName) > 0){
throw new Error("需要Auto.js版本" + version + "以上才能运行");
},
compareVersion(v1, v2) {
v1 = this.parseVersion(v1);
v2 = this.parseVersion(v2);
if (v1.major !== v2.major) {
return this.compare(v1.major, v2.major);
}
}
}
if (v1.minor !== v2.minor) {
return this.compare(v1.minor, v2.minor);
}
if (v1.revision !== v2.revision) {
return this.compare(v1.revision, v2.revision);
}
if (v1.buildType !== v2.buildType) {
return this.compare(v1.buildType, v2.buildType);
}
return this.compare(v1.build, v2.build);
},
compare(a, b) {
return a > b ? 1 : a < b ? -1 : 0;
},
parseVersion(v) {
const m = /(\d+)\.(\d+)\.(\d+)[ ]?(Alpha|Beta)?(\d*)/.exec(v);
if (!m) {
throw new Error('版本格式不合法: ' + v);
}
return {
major: parseInt(m[1]),
minor: parseInt(m[2]),
revision: parseInt(m[3]),
buildType: _.buildType(m[4]),
build: m[5] ? parseInt(m[5]) : 1,
};
},
buildType(str) {
if (str === 'Alpha') {
return this.buildTypes.alpha;
}
if (str === 'Beta') {
return this.buildTypes.beta;
}
return this.buildTypes.release;
},
};
var buildTypes = {
release: 100,
beta: 50,
alpha: 0
}
Object.assign(global, {
de: Packages.de,
okhttp3: Packages.okhttp3,
androidx: Packages.androidx,
toast(msg, is_long, is_forcible) {
let $ = {
cache: null,
toast(msg, is_long, is_forcible) {
this.init(arguments);
this.post();
},
/** @param {IArguments} args */
init(args) {
let [msg, is_long, is_forcible] = args;
this.message = isNullish(msg) ? '' : msg.toString();
this.is_long = this.parseIsLong(is_long);
this.is_forcible = is_forcible;
},
post() {
ui.post(() => {
new android.os.Handler(Looper.getMainLooper()).post(new Runnable({
run() {
$.is_forcible && $.dismiss();
$.cache = Toast.makeText(context, $.message, $.is_long);
$.show();
},
}));
});
},
parseIsLong(is_long) {
if (typeof is_long === 'number') {
return Number(!!is_long);
}
if (typeof is_long === 'string') {
return Number(/^l(ong)?$/i.test(is_long));
}
if (typeof is_long === 'boolean') {
return Number(is_long);
}
return 0;
},
dismiss() {
if (this.cache instanceof Toast) {
this.cache.cancel();
this.cache = null;
}
},
show() {
this.cache.show();
},
};
function compareVersion(v1, v2){
v1 = parseVersion(v1);
v2 = parseVersion(v2);
log(v1, v2);
return v1.major != v2.major ? compare(v1.major, v2.major) :
v1.minor != v2.minor ? compare(v1.minor, v2.minor) :
v1.revision != v2.revision ? compare(v1.revision, v2.revision) :
v1.buildType != v2.buildType ? compare(v1.buildType, v2.buildType) :
compare(v1.build, v2.build);
}
(function $LazY() {
this.toast = $.toast.bind($);
this.toast.dismiss = () => $.dismiss();
return this.toast;
}.call(this)).apply(null, arguments);
},
toastLog(msg, is_long, is_forcible) {
this.toast.apply(this, arguments);
this.log(msg);
},
sleep(millis_min, millis_max) {
let $ = {
rex_num: /[+-]?(\d+(\.\d+)?(e\d+)?)/,
set min(v) {
this._min = Number(v);
},
get min() {
return Math.max(this._min, 0);
},
set max(v) {
this._max = Number(v);
},
get max() {
return Math.min(this._max, Number.MAX_SAFE_INTEGER);
},
sleep(min, max) {
if (this.trigger()) {
this.parseArgs(min, max);
runtime.sleep(this.min + this.rand_bound);
}
},
trigger() {
if (ui.isUiThread()) {
throw Error('不能在ui线程执行阻塞操作请使用setTimeout代替');
}
return true;
},
parseArgs(min, max) {
this.parseMin(min);
this.parseMax(max);
this.parseRandBound();
},
parseMin(min) {
if (typeof min !== 'number') {
throw TypeError('Type of millis_min must be a number');
}
this.min = min;
},
parseMax(max) {
if (typeof max === 'number') {
this.max = max;
} else if (typeof max === 'string') {
let matched = max.match(this.rex_num);
if (matched === null) {
throw TypeError('String millis_max must have a number contained');
}
let delta = Number(matched[0]);
this.max = this.min + delta;
this.min = this.min - delta;
} else {
this.max = this.min;
}
},
parseRandBound() {
this.rand_bound = Math.ceil(Math.random() * (this.max - this.min));
},
};
function compare(a, b){
return a > b ? 1 :
a < b ? -1:
0;
}
function parseVersion(v){
var m = /(\d+)\.(\d+)\.(\d+)[ ]?(Alpha|Beta)?(\d*)/.exec(v);
if(!m){
throw new Error("版本格式不合法: " + v);
}
return {
major: parseInt(m[1]),
minor: parseInt(m[2]),
revision: parseInt(m[3]),
buildType: buildType(m[4]),
build: m[5] ? parseInt(m[5]) : 1
};
}
function buildType(str){
if(str == 'Alpha'){
return buildTypes.alpha;
}
if(str == 'Beta'){
return buildTypes.beta;
}
return buildTypes.release;
}
}
(function $LazY() {
return this.sleep = $.sleep.bind($);
}).call(this).call(null, millis_min, millis_max);
},
isStopped() {
return runtime.isStopped();
},
notStopped() {
return !this.isStopped();
},
isRunning() {
return this.notStopped();
},
exit() {
return runtime.exit.apply(runtime, arguments);
},
stop() {
this.exit();
},
setClip(text) {
return runtime.setClip(text);
},
getClip() {
return runtime.getClip();
},
currentPackage() {
this.auto();
return runtime.info.getLatestPackage();
},
currentActivity() {
this.auto();
return runtime.info.getLatestActivity();
},
waitForActivity(activity, period) {
_.ensureNonUiThread();
period = period || 200;
while (this.currentActivity() !== activity) {
sleep(period);
}
},
waitForPackage(packageName, period) {
_.ensureNonUiThread();
period = period || 200;
while (this.currentPackage() !== packageName) {
sleep(period);
}
},
random(min, max) {
if (arguments.length === 0) {
return Math.random();
}
return Math.floor(Math.random() * (max - min + 1)) + min;
},
setScreenMetrics() {
return runtime.setScreenMetrics.apply(runtime, arguments);
},
requiresApi() {
return runtime.requiresApi.apply(runtime, arguments);
},
requiresAutojsVersion(version) {
if (typeof version === 'number') {
if (_.compare(version, app.autojs.versionCode) > 0) {
throw new Error('需要Auto.js版本号' + version + '以上才能运行');
}
} else {
if (_.compareVersion(version, app.autojs.versionName) > 0) {
throw new Error('需要Auto.js版本' + version + '以上才能运行');
}
}
},
isPlainObject(o) {
return Object.prototype.toString.call(o).slice(8, -1) === 'Object';
},
isInteger(o) {
return Number.isInteger(o);
},
isNullish(o) {
// nullish coalescing operator: ??
return o === null || o === undefined;
},
isPrimitive(o) {
return o !== Object(o);
},
isReference(o) {
return o === Object(o);
},
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
module.exports = {
fill() {
if (!Object.getOwnPropertyDescriptors) {
/**
* @param {Object} o
* @return {Object.<string,PropertyDescriptor>} <!-- or {PropertyDescriptorMap} -->
*/
Object.getOwnPropertyDescriptors = function (o) {
let _descriptor = {};
Object.getOwnPropertyNames(o).forEach((k) => {
_descriptor[k] = Object.getOwnPropertyDescriptor(o, k);
});
return _descriptor;
};
}
if (!Array.prototype.flat) {
Object.defineProperty(Array.prototype, 'flat', {
value(depth) {
return (function _flat(arr, d) {
return d <= 0 ? arr : arr.reduce((a, b) => {
return a.concat(Array.isArray(b) ? _flat(b, d - 1) : b);
}, []);
})(this.slice(), depth || 1);
},
});
}
},
};

View File

@@ -25,6 +25,7 @@ import com.stardust.util.UiHandler;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -175,7 +176,7 @@ public class ScriptEngineService {
@Subscribe
public void onScriptExecution(ScriptExecutionEvent event) {
if (event.getCode() == ScriptExecutionEvent.ON_START) {
mGlobalConsole.verbose(mContext.getString(R.string.text_start_running) + "[" + event.getMessage() + "]");
mGlobalConsole.verbose(MessageFormat.format("{0} [{1}].", 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());
}

View File

@@ -1,5 +1,7 @@
package com.stardust.autojs.core.console;
import android.annotation.SuppressLint;
import com.stardust.util.UiHandler;
import org.apache.log4j.Level;
@@ -15,6 +17,7 @@ import java.util.Locale;
* Created by Stardust on 2017/10/22.
*/
@SuppressLint("ConstantLocale")
public class GlobalConsole extends ConsoleImpl {
private static final String LOG_tAG = "GlobalConsole";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault());
@@ -37,7 +40,6 @@ public class GlobalConsole extends ConsoleImpl {
private Priority toLog4jLevel(int level) {
switch (level) {
case android.util.Log.VERBOSE:
return Level.DEBUG;
case android.util.Log.DEBUG:
return Level.DEBUG;
case android.util.Log.INFO:
@@ -53,22 +55,15 @@ public class GlobalConsole extends ConsoleImpl {
}
private String getLevelChar(int level) {
switch (level) {
case android.util.Log.VERBOSE:
return "V";
case android.util.Log.DEBUG:
return "D";
case android.util.Log.INFO:
return "I";
case android.util.Log.WARN:
return "W";
case android.util.Log.ERROR:
return "E";
case android.util.Log.ASSERT:
return "A";
}
return "";
return switch (level) {
case android.util.Log.VERBOSE -> "V";
case android.util.Log.DEBUG -> "D";
case android.util.Log.INFO -> "I";
case android.util.Log.WARN -> "W";
case android.util.Log.ERROR -> "E";
case android.util.Log.ASSERT -> "A";
default -> "";
};
}
}

View File

@@ -35,41 +35,39 @@ public class Loopers implements MessageQueue.IdleHandler {
private static final Runnable EMPTY_RUNNABLE = () -> {
};
private volatile ThreadLocal<Boolean> waitWhenIdle = new ThreadLocal<Boolean>() {
private final ThreadLocal<Boolean> waitWhenIdle = new ThreadLocal<>() {
@Nullable
@Override
protected Boolean initialValue() {
return Looper.myLooper() == Looper.getMainLooper();
}
};
private volatile ThreadLocal<HashSet<Integer>> waitIds = new ThreadLocal<HashSet<Integer>>() {
private final ThreadLocal<HashSet<Integer>> waitIds = new ThreadLocal<>() {
@Nullable
@Override
protected HashSet<Integer> initialValue() {
return new HashSet<>();
}
};
private volatile ThreadLocal<Integer> maxWaitId = new ThreadLocal<Integer>() {
private final ThreadLocal<Integer> maxWaitId = new ThreadLocal<>() {
@Nullable
@Override
protected Integer initialValue() {
return 0;
}
};
private volatile ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>> looperQuitHandlers = new ThreadLocal<>();
private final ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>> looperQuitHandlers = new ThreadLocal<>();
private volatile Looper mServantLooper;
private Timers mTimers;
private ScriptRuntime mScriptRuntime;
private final Timers mTimers;
private LooperQuitHandler mMainLooperQuitHandler;
private Handler mMainHandler;
private Looper mMainLooper;
private Threads mThreads;
private MessageQueue mMainMessageQueue;
private final Handler mMainHandler;
private final Looper mMainLooper;
private final Threads mThreads;
private final MessageQueue mMainMessageQueue;
public Loopers(ScriptRuntime runtime) {
mTimers = runtime.timers;
mThreads = runtime.threads;
mScriptRuntime = runtime;
prepare();
mMainLooper = Looper.myLooper();
mMainHandler = new Handler();
@@ -122,9 +120,9 @@ public class Loopers implements MessageQueue.IdleHandler {
private void initServantThread() {
final Object lock = Loopers.this;
new ThreadCompat(() -> {
Looper.prepare();
final Object lock = Loopers.this;
mServantLooper = Looper.myLooper();
synchronized (lock) {
lock.notifyAll();

View File

@@ -14,12 +14,10 @@ import com.stardust.concurrent.VolatileBox;
public class Timer {
private static final String LOG_TAG = "Timer";
private SparseArray<Runnable> mHandlerCallbacks = new SparseArray<>();
private final SparseArray<Runnable> mHandlerCallbacks = new SparseArray<>();
private int mCallbackMaxId = 0;
private ScriptRuntime mRuntime;
private Handler mHandler;
private final ScriptRuntime mRuntime;
private final Handler mHandler;
private long mMaxCallbackUptimeMillis = 0;
private final VolatileBox<Long> mMaxCallbackMillisForAllThread;
@@ -39,7 +37,7 @@ public class Timer {
mCallbackMaxId++;
final int id = mCallbackMaxId;
Runnable r = () -> {
callFunction(callback, null, args);
callFunction(callback, args);
mHandlerCallbacks.remove(id);
};
mHandlerCallbacks.put(id, r);
@@ -47,15 +45,15 @@ public class Timer {
return id;
}
private void callFunction(Object callback, Object thiz, Object[] args) {
private void callFunction(Object callback, Object[] args) {
if(Looper.myLooper() == Looper.getMainLooper()){
try {
mRuntime.bridges.callFunction(callback, thiz, args);
mRuntime.bridges.callFunction(callback, null, args);
}catch (Exception e){
mRuntime.exit(e);
}
}else {
mRuntime.bridges.callFunction(callback, thiz, args);
mRuntime.bridges.callFunction(callback, null, args);
}
}
@@ -71,7 +69,7 @@ public class Timer {
public void run() {
if (mHandlerCallbacks.get(id) == null)
return;
callFunction(listener, null, args);
callFunction(listener, args);
postDelayed(this, interval);
}
};
@@ -101,7 +99,7 @@ public class Timer {
mCallbackMaxId++;
final int id = mCallbackMaxId;
Runnable r = () -> {
callFunction(listener, null, args);
callFunction(listener, args);
mHandlerCallbacks.remove(id);
};
mHandlerCallbacks.put(id, r);

View File

@@ -3,6 +3,7 @@ package com.stardust.autojs.core.looper;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import com.stardust.autojs.engine.RhinoJavaScriptEngine;
import com.stardust.autojs.runtime.ScriptRuntime;
@@ -18,12 +19,12 @@ import java.util.concurrent.ConcurrentHashMap;
public class TimerThread extends ThreadCompat {
private static ConcurrentHashMap<Thread, Timer> sTimerMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Thread, Timer> sTimerMap = new ConcurrentHashMap<>();
private Timer mTimer;
private final VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads;
private final ScriptRuntime mRuntime;
private Runnable mTarget;
private final Runnable mTarget;
private boolean mRunning = false;
private final Object mRunningLock = new Object();
@@ -46,7 +47,7 @@ public class TimerThread extends ThreadCompat {
Looper.loop();
} catch (Throwable e) {
if (!ScriptInterruptedException.causedByInterrupted(e)) {
mRuntime.console.error(Thread.currentThread().toString() + ": ", e);
mRuntime.console.error(Thread.currentThread() + ": ", e);
}
} finally {
onExit();
@@ -121,6 +122,7 @@ public class TimerThread extends ThreadCompat {
}
}
@NonNull
@Override
public String toString() {
return "Thread[" + getName() + "," + getPriority() + "]";

View File

@@ -2,8 +2,9 @@ package com.stardust.autojs.core.ui.inflater.inflaters;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.os.Build;
import androidx.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -135,31 +136,17 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
case "width":
case "layout_width":
switch (value) {
case "wrap_content":
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
break;
case "fill_parent":
case "match_parent":
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
break;
default:
layoutParams.width = Dimensions.parseToPixel(value, view, parent, true);
break;
case "wrap_content" -> layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
case "fill_parent", "match_parent" -> layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
default -> layoutParams.width = Dimensions.parseToPixel(value, view, parent, true);
}
break;
case "height":
case "layout_height":
switch (value) {
case "wrap_content":
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
break;
case "fill_parent":
case "match_parent":
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
break;
default:
layoutParams.height = Dimensions.parseToPixel(value, view, parent, false);
break;
case "wrap_content" -> layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
case "fill_parent", "match_parent" -> layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
default -> layoutParams.height = Dimensions.parseToPixel(value, view, parent, false);
}
break;
case "layout_gravity":
@@ -168,7 +155,7 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
} else if (parent instanceof FrameLayout) {
((FrameLayout.LayoutParams) layoutParams).gravity = Gravities.parse(value);
} else {
return setLayoutGravity(parent, view, Gravities.parse(value));
return setLayoutGravity(view, Gravities.parse(value));
}
break;
case "layout_weight":
@@ -234,37 +221,30 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
layoutRule = RelativeLayout.CENTER_IN_PARENT;
break;
case "layout_margin":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams params) {
int margin = Dimensions.parseToIntPixel(value, view);
params.bottomMargin = params.leftMargin = params.topMargin = params.rightMargin = margin;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
params.setMarginStart(margin);
params.setMarginEnd(margin);
}
params.setMarginStart(margin);
params.setMarginEnd(margin);
}
break;
case "layout_marginLeft":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams params) {
params.leftMargin = Dimensions.parseToIntPixel(value, view);
}
break;
case "layout_marginTop":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams params) {
params.topMargin = Dimensions.parseToIntPixel(value, view);
}
break;
case "layout_marginRight":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams params) {
params.rightMargin = Dimensions.parseToIntPixel(value, view);
}
break;
case "layout_marginBottom":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams params) {
params.bottomMargin = Dimensions.parseToIntPixel(value, view);
}
break;
@@ -293,21 +273,13 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
case "accessibilityTraversalBefore":
Exceptions.unsupports(view, attr, value);
case "alpha":
view.setAlpha(Float.valueOf(value));
break;
case "autofillHints":
case "autofilledHighlight":
Exceptions.unsupports(view, attr, value);
view.setAlpha(Float.parseFloat(value));
break;
case "backgroundTint":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setBackgroundTintList(ColorStateList.valueOf(Colors.parse(view, value)));
}
view.setBackgroundTintList(ColorStateList.valueOf(Colors.parse(view, value)));
break;
case "backgroundTintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setBackgroundTintMode(TINT_MODES.get(value));
}
view.setBackgroundTintMode(TINT_MODES.get(value));
break;
case "checked":
if (view instanceof CompoundButton) {
@@ -315,106 +287,73 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
}
break;
case "clickable":
view.setClickable(Boolean.valueOf(value));
view.setClickable(Boolean.parseBoolean(value));
break;
case "contentDescription":
view.setContentDescription(Strings.parse(view, value));
break;
case "contextClickable":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setContextClickable(Boolean.valueOf(value));
}
break;
case "defaultFocusHighlightEnabled":
Exceptions.unsupports(view, attr, value);
view.setContextClickable(Boolean.parseBoolean(value));
break;
case "drawingCacheQuality":
view.setDrawingCacheQuality(DRAWABLE_CACHE_QUALITIES.get(value));
break;
case "duplicateParentState":
view.setDuplicateParentStateEnabled(Boolean.valueOf(value));
view.setDuplicateParentStateEnabled(Boolean.parseBoolean(value));
break;
case "elevation":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setElevation(Dimensions.parseToIntPixel(value, view));
}
view.setElevation(Dimensions.parseToIntPixel(value, view));
break;
case "fadeScrollbars":
view.setScrollbarFadingEnabled(Boolean.valueOf(value));
view.setScrollbarFadingEnabled(Boolean.parseBoolean(value));
break;
case "fadingEdgeLength":
view.setFadingEdgeLength(Dimensions.parseToIntPixel(value, view));
break;
case "filterTouchesWhenObscured":
view.setFilterTouchesWhenObscured(Boolean.valueOf(value));
view.setFilterTouchesWhenObscured(Boolean.parseBoolean(value));
break;
case "fitsSystemWindows":
view.setFitsSystemWindows(Boolean.valueOf(value));
view.setFitsSystemWindows(Boolean.parseBoolean(value));
break;
case "focusable":
view.setFocusable(Boolean.valueOf(value));
view.setFocusable(Boolean.parseBoolean(value));
break;
case "focusableInTouchMode":
view.setFocusableInTouchMode(Boolean.valueOf(value));
break;
case "focusedByDefault":
Exceptions.unsupports(view, attr, value);
view.setFocusableInTouchMode(Boolean.parseBoolean(value));
break;
case "forceHasOverlappingRendering":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
view.forceHasOverlappingRendering(Boolean.valueOf(value));
}
view.forceHasOverlappingRendering(Boolean.parseBoolean(value));
break;
case "foreground":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setForeground(getDrawables().parse(view, value));
}
view.setForeground(getDrawables().parse(view, value));
break;
case "foregroundGravity":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setForegroundGravity(Gravities.parse(value));
}
view.setForegroundGravity(Gravities.parse(value));
break;
case "foregroundTint":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setForegroundTintList(ColorStateList.valueOf(Colors.parse(view, value)));
}
view.setForegroundTintList(ColorStateList.valueOf(Colors.parse(view, value)));
break;
case "foregroundTintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setForegroundTintMode(TINT_MODES.get(value));
}
view.setForegroundTintMode(TINT_MODES.get(value));
break;
case "hapticFeedbackEnabled":
view.setHapticFeedbackEnabled(Boolean.valueOf(value));
view.setHapticFeedbackEnabled(Boolean.parseBoolean(value));
break;
case "importantForAccessibility":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY.get(value));
}
break;
case "importantForAutofill":
Exceptions.unsupports(view, attr, value);
view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY.get(value));
break;
case "isScrollContainer":
view.setScrollContainer(Boolean.valueOf(value));
view.setScrollContainer(Boolean.parseBoolean(value));
break;
case "keepScreenOn":
view.setKeepScreenOn(Boolean.valueOf(value));
break;
case "keyboardNavigationCluster":
Exceptions.unsupports(view, attr, value);
break;
case "layerType":
Exceptions.unsupports(view, attr, value);
view.setKeepScreenOn(Boolean.parseBoolean(value));
break;
case "layoutDirection":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setLayoutDirection(LAYOUT_DIRECTIONS.get(value));
}
view.setLayoutDirection(LAYOUT_DIRECTIONS.get(value));
break;
case "longClickable":
view.setLongClickable(Boolean.valueOf(value));
view.setLongClickable(Boolean.parseBoolean(value));
break;
case "minHeight":
view.setMinimumHeight(Dimensions.parseToIntPixel(value, view));
@@ -422,48 +361,13 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
case "minWidth":
view.setMinimumWidth(Dimensions.parseToIntPixel(value, view));
break;
case "nextClusterForward":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusDown":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusForward":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusLeft":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusRight":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusUp":
Exceptions.unsupports(view, attr, value);
break;
case "onClick":
Exceptions.unsupports(view, attr, value);
break;
case "paddingEnd":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(),
Dimensions.parseToIntPixel(value, view), view.getPaddingBottom());
} else {
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
Dimensions.parseToIntPixel(value, view), view.getPaddingBottom());
}
break;
case "paddingHorizontal":
case "paddingVertical":
Exceptions.unsupports(view, attr, value);
view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(),
Dimensions.parseToIntPixel(value, view), view.getPaddingBottom());
break;
case "paddingStart":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setPaddingRelative(Dimensions.parseToIntPixel(value, view), view.getPaddingTop(),
view.getPaddingEnd(), view.getPaddingBottom());
} else {
view.setPadding(Dimensions.parseToIntPixel(value, view), view.getPaddingTop(),
view.getPaddingRight(), view.getPaddingBottom());
}
view.setPaddingRelative(Dimensions.parseToIntPixel(value, view), view.getPaddingTop(),
view.getPaddingEnd(), view.getPaddingBottom());
break;
case "requiresFadingEdge":
for (String str : value.split("\\|")) {
@@ -484,7 +388,7 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
view.setRotationY(Float.parseFloat(value));
break;
case "saveEnabled":
view.setSaveEnabled(Boolean.valueOf(value));
view.setSaveEnabled(Boolean.parseBoolean(value));
break;
case "scaleX":
view.setScaleX(Float.parseFloat(value));
@@ -493,9 +397,7 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
view.setScaleY(Float.parseFloat(value));
break;
case "scrollIndicators":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setScrollIndicators(SCROLL_INDICATORS.get(value));
}
view.setScrollIndicators(SCROLL_INDICATORS.get(value));
break;
case "scrollX":
view.setScrollX(Dimensions.parseToIntPixel(value, view));
@@ -503,24 +405,14 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
case "scrollY":
view.setScrollY(Dimensions.parseToIntPixel(value, view));
break;
case "scrollbarAlwaysDrawHorizontalTrack":
case "scrollbarAlwaysDrawVerticalTrack":
Exceptions.unsupports(view, attr, value);
break;
case "scrollbarDefaultDelayBeforeFade":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setScrollBarDefaultDelayBeforeFade(Integer.valueOf(value));
}
view.setScrollBarDefaultDelayBeforeFade(Integer.parseInt(value));
break;
case "scrollbarFadeDuration":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setScrollBarFadeDuration(Integer.valueOf(value));
}
view.setScrollBarFadeDuration(Integer.parseInt(value));
break;
case "scrollbarSize":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setScrollBarSize(Dimensions.parseToIntPixel(value, view));
}
view.setScrollBarSize(Dimensions.parseToIntPixel(value, view));
break;
case "scrollbarStyle":
view.setScrollBarStyle(SCROLLBARS_STYLES.get(value));
@@ -531,7 +423,7 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
case "scrollbarTrackVertical":
Exceptions.unsupports(view, attr, value);
case "scrollbars":
for (String str : value.split("|")) {
for (String str : value.split("\\|")) {
if (str.equals("horizontal")) {
view.setHorizontalScrollBarEnabled(true);
} else if (str.equals("vertical")) {
@@ -540,7 +432,7 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
}
break;
case "soundEffectsEnabled":
view.setSoundEffectsEnabled(Boolean.valueOf(value));
view.setSoundEffectsEnabled(Boolean.parseBoolean(value));
break;
case "stateListAnimator":
Exceptions.unsupports(view, attr, value);
@@ -548,21 +440,10 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
view.setTag(Strings.parse(view, value));
break;
case "textAlignment":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setTextAlignment(TEXT_ALIGNMENTS.get(value));
}
view.setTextAlignment(TEXT_ALIGNMENTS.get(value));
break;
case "textDirection":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setTextDirection(TEXT_DIRECTIONS.get(value));
}
break;
case "theme":
//Exceptions.unsupports(view, attr, value);
break;
case "tooltipText":
Exceptions.unsupports(view, attr, value);
view.setTextDirection(TEXT_DIRECTIONS.get(value));
break;
case "transformPivotX":
view.setPivotX(Dimensions.parseToPixel(value, view));
@@ -571,9 +452,7 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
view.setPivotY(Dimensions.parseToPixel(value, view));
break;
case "transitionName":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setTransitionName(Strings.parse(view, value));
}
view.setTransitionName(Strings.parse(view, value));
break;
case "translationX":
view.setTranslationX(Dimensions.parseToPixel(value, view));
@@ -582,16 +461,34 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
view.setTranslationY(Dimensions.parseToPixel(value, view));
break;
case "translationZ":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setTranslationZ(Dimensions.parseToPixel(value, view));
}
view.setTranslationZ(Dimensions.parseToPixel(value, view));
break;
case "visibility":
view.setVisibility(VISIBILITY.get(value));
break;
case "autofillHints":
case "autofilledHighlight":
case "tooltipText":
case "defaultFocusHighlightEnabled":
case "focusedByDefault":
case "importantForAutofill":
case "keyboardNavigationCluster":
case "layerType":
case "nextClusterForward":
case "nextFocusDown":
case "nextFocusForward":
case "nextFocusLeft":
case "nextFocusRight":
case "nextFocusUp":
case "onClick":
case "paddingHorizontal":
case "paddingVertical":
case "scrollbarAlwaysDrawHorizontalTrack":
case "scrollbarAlwaysDrawVerticalTrack":
Exceptions.unsupports(view, attr, value);
break;
default:
return false;
}
if (layoutRule != null && parent instanceof RelativeLayout) {
if (layoutTarget) {
@@ -604,7 +501,7 @@ public class BaseViewInflater<V extends View> implements ViewInflater<V> {
return true;
}
public boolean setLayoutGravity(ViewGroup parent, V view, int gravity) {
public boolean setLayoutGravity(V view, int gravity) {
try {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
Field field = layoutParams.getClass().getField("gravity");

View File

@@ -1,7 +1,6 @@
package com.stardust.autojs.core.ui.inflater.inflaters;
import android.animation.LayoutTransition;
import android.os.Build;
import android.view.ViewGroup;
import com.stardust.autojs.core.ui.inflater.ResourceParser;
@@ -39,22 +38,16 @@ public class ViewGroupInflater<V extends ViewGroup> extends BaseViewInflater<V>
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
switch (attr) {
case "addStatesFromChildren":
view.setAddStatesFromChildren(Boolean.valueOf(value));
break;
case "alwaysDrawnWithCache":
view.setAlwaysDrawnWithCacheEnabled(Boolean.valueOf(value));
view.setAddStatesFromChildren(Boolean.parseBoolean(value));
break;
case "animateLayoutChanges":
view.setLayoutTransition(new LayoutTransition());
break;
case "animationCache":
view.setAnimationCacheEnabled(Boolean.valueOf(value));
break;
case "clipChildren":
view.setClipChildren(Boolean.valueOf(value));
view.setClipChildren(Boolean.parseBoolean(value));
break;
case "clipToPadding":
view.setClipToPadding(Boolean.valueOf(value));
view.setClipToPadding(Boolean.parseBoolean(value));
break;
case "descendantFocusability":
view.setDescendantFocusability(DESCENDANT_FOCUSABILITY.get(value));
@@ -63,15 +56,13 @@ public class ViewGroupInflater<V extends ViewGroup> extends BaseViewInflater<V>
Exceptions.unsupports(view, attr, value);
break;
case "layoutMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
view.setLayoutMode(LAYOUT_MODES.get(value));
}
view.setLayoutMode(LAYOUT_MODES.get(value));
break;
case "persistentDrawingCache":
view.setPersistentDrawingCache(PERSISTENT_DRAWING_CACHE.get(value));
break;
case "splitMotionEvents":
view.setMotionEventSplittingEnabled(Boolean.valueOf(value));
view.setMotionEventSplittingEnabled(Boolean.parseBoolean(value));
break;
default:
return super.setAttr(view, attr, value, parent, attrs);

View File

@@ -9,11 +9,12 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.Base64;
import android.view.View;
import android.widget.ImageView;
import androidx.appcompat.content.res.AppCompatResources;
import com.stardust.autojs.core.ui.inflater.ImageLoader;
import java.net.URL;
@@ -41,7 +42,6 @@ public class Drawables {
}
public Drawable parse(Context context, String value) {
Resources resources = context.getResources();
if (value.startsWith("@color/") || value.startsWith("@android:color/") || value.startsWith("#")) {
return new ColorDrawable(Colors.parse(context, value));
}
@@ -49,30 +49,29 @@ public class Drawables {
return loadAttrResources(context, value);
}
if (value.startsWith("file://")) {
return decodeImage(value.substring(7));
return decodeImage(context, value.substring(7));
}
return loadDrawableResources(context, value);
}
public Drawable loadDrawableResources(Context context, String value) {
int resId = context.getResources().getIdentifier(value, "drawable",
context.getPackageName());
if (resId == 0)
int resId = context.getResources().getIdentifier(value, "drawable", context.getPackageName());
if (resId == 0) {
throw new Resources.NotFoundException("drawable not found: " + value);
return context.getResources().getDrawable(resId);
}
return AppCompatResources.getDrawable(context, resId);
}
public Drawable loadAttrResources(Context context, String value) {
int[] attr = {context.getResources().getIdentifier(value.substring(1), "attr",
context.getPackageName())};
int[] attr = {context.getResources().getIdentifier(value.substring(1), "attr", context.getPackageName())};
TypedArray ta = context.obtainStyledAttributes(attr);
Drawable drawable = ta.getDrawable(0 /* index */);
ta.recycle();
return drawable;
}
public Drawable decodeImage(String path) {
return new BitmapDrawable(BitmapFactory.decodeFile(path));
public Drawable decodeImage(Context context, String path) {
return new BitmapDrawable(context.getResources(), BitmapFactory.decodeFile(path));
}
public Drawable parse(View view, String name) {
@@ -119,11 +118,7 @@ public class Drawables {
if (value.startsWith("http://") || value.startsWith("https://")) {
loadIntoBackground(view, Uri.parse(value));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setBackground(parse(view, value));
} else {
view.setBackgroundDrawable(parse(view, value));
}
view.setBackground(parse(view, value));
}
}

View File

@@ -15,8 +15,10 @@ import android.os.Build;
import android.os.PowerManager;
import android.os.Vibrator;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.telephony.TelephonyManager;
import com.stardust.autojs.R;
@@ -73,13 +75,8 @@ public class Device {
public static final String securityPatch;
static {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
baseOS = Build.VERSION.BASE_OS;
securityPatch = Build.VERSION.SECURITY_PATCH;
} else {
baseOS = null;
securityPatch = null;
}
baseOS = Build.VERSION.BASE_OS;
securityPatch = Build.VERSION.SECURITY_PATCH;
}
public static final String codename = Build.VERSION.CODENAME;
@@ -87,7 +84,7 @@ public class Device {
@SuppressLint("HardwareIds")
public static final String serial = Build.SERIAL;
private Context mContext;
private final Context mContext;
private PowerManager.WakeLock mWakeLock;
private int mWakeLockFlag;
@@ -285,16 +282,13 @@ public class Device {
return;
}
SettingsCompat.manageWriteSettings(mContext);
throw new SecurityException(mContext.getString(R.string.no_write_settings_permissin));
throw new SecurityException(mContext.getString(R.string.no_write_settings_permission));
}
private void checkReadPhoneStatePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (mContext.checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(mContext.getString(R.string.no_read_phone_state_permissin));
}
if (mContext.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(mContext.getString(R.string.no_read_phone_state_permissin));
}
}
@@ -323,7 +317,9 @@ public class Device {
return getMacByFile();
}
@SuppressLint("MissingPermission")
String mac = wifiInf.getMacAddress();
if (FAKE_MAC_ADDRESS.equals(mac)) {
mac = null;
}

View File

@@ -7,24 +7,23 @@ import android.graphics.Point;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import androidx.annotation.RequiresApi;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.stardust.autojs.R;
import com.stardust.autojs.core.accessibility.AccessibilityBridge;
import com.stardust.autojs.core.boardcast.BroadcastEmitter;
import com.stardust.autojs.core.eventloop.EventEmitter;
import com.stardust.autojs.core.looper.Loopers;
import com.stardust.autojs.core.looper.MainThreadProxy;
import com.stardust.autojs.core.looper.Timer;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.notification.Notification;
import com.stardust.notification.NotificationListenerService;
import com.stardust.autojs.runtime.exception.ScriptException;
import com.stardust.autojs.core.inputevent.InputEventObserver;
import com.stardust.autojs.core.inputevent.TouchObserver;
import com.stardust.autojs.core.looper.Loopers;
import com.stardust.autojs.core.looper.Timer;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.autojs.runtime.exception.ScriptException;
import com.stardust.notification.Notification;
import com.stardust.notification.NotificationListenerService;
import com.stardust.util.MapBuilder;
import com.stardust.view.accessibility.AccessibilityNotificationObserver;
import com.stardust.view.accessibility.AccessibilityService;
@@ -63,21 +62,20 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
.put(AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT, "down_right")
.build();
private AccessibilityBridge mAccessibilityBridge;
private Context mContext;
private final AccessibilityBridge mAccessibilityBridge;
private final Context mContext;
private TouchObserver mTouchObserver;
private long mLastTouchEventMillis;
private long mTouchEventTimeout = 10;
private boolean mListeningKey = false;
private Loopers mLoopers;
private final Loopers mLoopers;
private Handler mHandler;
private boolean mListeningNotification = false;
private boolean mListeningGesture = false;
private boolean mListeningToast = false;
private ScriptRuntime mScriptRuntime;
private final ScriptRuntime mScriptRuntime;
private volatile boolean mInterceptsAllKey = false;
private KeyInterceptor mKeyInterceptor;
private Set<String> mInterceptedKeys = new HashSet<>();
private final Set<String> mInterceptedKeys = new HashSet<>();
public final BroadcastEmitter broadcast;
@@ -99,9 +97,6 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
return new EventEmitter(mBridges, timer);
}
public EventEmitter emitter(MainThreadProxy mainThreadProxy) {
return new EventEmitter(mBridges, mScriptRuntime.timers.getMainTimer());
}
public void observeKey() {
if (mListeningKey)
@@ -227,9 +222,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
ensureHandler();
mLoopers.waitWhenIdle(true);
if (NotificationListenerService.Companion.getInstance() == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
mContext.startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
}
throw new ScriptException(mContext.getString(R.string.exception_notification_service_disabled));
}
NotificationListenerService.Companion.getInstance().addListener(this);
@@ -245,20 +238,6 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
mAccessibilityBridge.getNotificationObserver().addToastListener(this);
}
public void observeGesture() {
ScriptRuntime.requiresApi(Build.VERSION_CODES.O);
if (mListeningGesture) {
return;
}
AccessibilityService service = getAccessibilityService();
if ((service.getServiceInfo().flags & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) == 0) {
throw new ScriptException(mContext.getString(R.string.text_should_enable_gesture_observing));
}
service.getGestureEventDispatcher().addListener(this);
ensureHandler();
mLoopers.waitWhenIdle(true);
mListeningGesture = true;
}
public Events onNotification(Object listener) {
on("notification", listener);
@@ -286,8 +265,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
if (mListeningNotification) {
mAccessibilityBridge.getNotificationObserver().removeNotificationListener(this);
mAccessibilityBridge.getNotificationObserver().removeToastListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
&& NotificationListenerService.Companion.getInstance() != null) {
if (NotificationListenerService.Companion.getInstance() != null) {
NotificationListenerService.Companion.getInstance().removeListener(this);
}
}
@@ -298,16 +276,10 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
}
mKeyInterceptor = null;
}
if (mListeningGesture) {
AccessibilityService service = mAccessibilityBridge.getService();
if (service != null) {
service.getGestureEventDispatcher().removeListener(this);
}
}
}
@Override
public void onKeyEvent(final int keyCode, final KeyEvent event) {
public void onKeyEvent(final int keyCode, @NonNull final KeyEvent event) {
mHandler.post(() -> {
String keyName = KeyEvent.keyCodeToString(keyCode).substring(8).toLowerCase();
emit(keyName, event);
@@ -331,13 +303,13 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
mHandler.post(() -> emit("touch", new Point(x, y)));
}
public void onNotification(final Notification notification) {
public void onNotification(@NonNull final Notification notification) {
mHandler.post(() -> emit("notification", notification));
}
@Override
public void onToast(final AccessibilityNotificationObserver.Toast toast) {
public void onToast(@NonNull final AccessibilityNotificationObserver.Toast toast) {
mHandler.post(() -> emit("toast", toast));
}

View File

@@ -55,13 +55,13 @@ public class Images {
private static final String TAG = Images.class.getSimpleName();
private ScriptRuntime mScriptRuntime;
private ScreenCaptureRequester mScreenCaptureRequester;
private final ScriptRuntime mScriptRuntime;
private final ScreenCaptureRequester mScreenCaptureRequester;
private ScreenCapturer mScreenCapturer;
private Context mContext;
private final Context mContext;
private Image mPreCapture;
private ImageWrapper mPreCaptureImage;
private ScreenMetrics mScreenMetrics;
private final ScreenMetrics mScreenMetrics;
private volatile boolean mOpenCvInitialized = false;
@ScriptVariable
@@ -169,11 +169,11 @@ public class Images {
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
if (direction == Gravity.LEFT || direction == Gravity.RIGHT) {
canvas.drawBitmap(img1.getBitmap(), 0, (height - img1.getHeight()) / 2, paint);
canvas.drawBitmap(img2.getBitmap(), img1.getWidth(), (height - img2.getHeight()) / 2, paint);
canvas.drawBitmap(img1.getBitmap(), 0, (float) (height - img1.getHeight()) / 2, paint);
canvas.drawBitmap(img2.getBitmap(), img1.getWidth(), (float) (height - img2.getHeight()) / 2, paint);
} else {
canvas.drawBitmap(img1.getBitmap(), (width - img1.getWidth()) / 2, 0, paint);
canvas.drawBitmap(img2.getBitmap(), (width - img2.getWidth()) / 2, img1.getHeight(), paint);
canvas.drawBitmap(img1.getBitmap(), (float) (width - img1.getWidth()) / 2, 0, paint);
canvas.drawBitmap(img2.getBitmap(), (float) (width - img2.getWidth()) / 2, img1.getHeight(), paint);
}
return ImageWrapper.ofBitmap(bitmap);
}
@@ -217,16 +217,12 @@ public class Images {
}
private Bitmap.CompressFormat parseImageFormat(String format) {
switch (format) {
case "png":
return Bitmap.CompressFormat.PNG;
case "jpeg":
case "jpg":
return Bitmap.CompressFormat.JPEG;
case "webp":
return Bitmap.CompressFormat.WEBP;
}
return null;
return switch (format) {
case "png" -> Bitmap.CompressFormat.PNG;
case "jpeg", "jpg" -> Bitmap.CompressFormat.JPEG;
case "webp" -> Bitmap.CompressFormat.WEBP;
default -> null;
};
}
public ImageWrapper load(String src) {
@@ -265,7 +261,7 @@ public class Images {
}
public void releaseScreenCapturer() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mScreenCapturer != null) {
if (mScreenCapturer != null) {
mScreenCapturer.release();
}
}
@@ -367,4 +363,4 @@ public class Images {
mOpenCvInitialized = true;
Log.i(TAG, "opencv: initialized");
}
}
}

View File

@@ -5,6 +5,7 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import androidx.annotation.NonNull;
import com.stardust.autojs.core.eventloop.EventEmitter;
@@ -24,7 +25,6 @@ import java.util.Set;
public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
public class SensorEventEmitter extends EventEmitter implements SensorEventListener {
public SensorEventEmitter(ScriptBridges bridges) {
@@ -58,25 +58,22 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
public static final int fastest = SensorManager.SENSOR_DELAY_FASTEST;
}
private static final Map<String, Integer> SENSORS = new MapBuilder<String, Integer>()
.put("ACCELEROMETER", Sensor.TYPE_ACCELEROMETER)
.put("MAGNETIC_FIELD", Sensor.TYPE_MAGNETIC_FIELD)
.put("ORIENTATION", Sensor.TYPE_ORIENTATION)
.put("AMBIENT_TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE)
.put("GRAVITY", Sensor.TYPE_GRAVITY)
.put("GYROSCOPE", Sensor.TYPE_GYROSCOPE)
.put("LIGHT", Sensor.TYPE_LIGHT)
.put("TEMPERATURE", Sensor.TYPE_TEMPERATURE)
.put("PRESSURE", Sensor.TYPE_PRESSURE)
.put("AMBIENT_TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE)
.put("PROXIMITY", Sensor.TYPE_PROXIMITY)
.put("GRAVITY", Sensor.TYPE_GRAVITY)
.put("LINEAR_ACCELERATION", Sensor.TYPE_LINEAR_ACCELERATION)
.put("MAGNETIC_FIELD", Sensor.TYPE_MAGNETIC_FIELD)
.put("ORIENTATION", Sensor.TYPE_ORIENTATION)
.put("PRESSURE", Sensor.TYPE_PRESSURE)
.put("PROXIMITY", Sensor.TYPE_PROXIMITY)
.put("RELATIVE_HUMIDITY", Sensor.TYPE_RELATIVE_HUMIDITY)
.put("AMBIENT_TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE)
.put("TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE)
.build();
public boolean ignoresUnsupportedSensor = false;
public final Delay delay = new Delay();
private final Set<SensorEventEmitter> mSensorEventEmitters = new HashSet<>();
private final SensorManager mSensorManager;
@@ -84,7 +81,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
private final SensorEventEmitter mNoOpSensorEventEmitter;
private final ScriptRuntime mScriptRuntime;
public Sensors(Context context, ScriptRuntime runtime) {
super(runtime.bridges);
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
@@ -99,8 +95,9 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
}
public SensorEventEmitter register(String sensorName, int delay) {
if (sensorName == null)
if (sensorName == null) {
throw new NullPointerException("sensorName = null");
}
Sensor sensor = getSensor(sensorName);
if (sensor == null) {
if (ignoresUnsupportedSensor) {
@@ -122,26 +119,19 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
return emitter;
}
@Override
public boolean shouldQuit() {
if (mSensorEventEmitters.isEmpty()) {
return true;
}
return false;
return mSensorEventEmitters.isEmpty();
}
public Sensor getSensor(String sensorName) {
Integer type = SENSORS.get(sensorName.toUpperCase());
if (type == null)
type = getSensorTypeByReflect(sensorName);
if (type == null)
return null;
return mSensorManager.getDefaultSensor(type);
sensorName = sensorName.toUpperCase();
Integer type = SENSORS.get(sensorName);
type = type == null ? getSensorTypeByReflect(sensorName) : type;
return type == null ? null : mSensorManager.getDefaultSensor(type);
}
private Integer getSensorTypeByReflect(String sensorName) {
sensorName = sensorName.toUpperCase();
try {
Field field = Sensor.class.getField("TYPE_" + sensorName);
return (Integer) field.get(null);
@@ -151,12 +141,12 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
}
public void unregister(SensorEventEmitter emitter) {
if (emitter == null)
return;
synchronized (mSensorEventEmitters) {
mSensorEventEmitters.remove(emitter);
if (emitter != null) {
synchronized (mSensorEventEmitters) {
mSensorEventEmitters.remove(emitter);
}
mSensorManager.unregisterListener(emitter);
}
mSensorManager.unregisterListener(emitter);
}
public void unregisterAll() {

View File

@@ -110,8 +110,8 @@ public class UI extends ProxyObject {
private class Drawables extends com.stardust.autojs.core.ui.inflater.util.Drawables {
@Override
public Drawable decodeImage(String path) {
return super.decodeImage(mRuntime.files.path(path));
public Drawable decodeImage(Context context, String path) {
return super.decodeImage(context, mRuntime.files.path(path));
}
}

View File

@@ -4,9 +4,9 @@
<string name="text_start_running">Script Running</string>
<string name="text_path_is_empty">Path is empty</string>
<string name="text_file_not_exists">File does not exist</string>
<string name="text_no_file_rw_permission">No file reading&amp;writing permission</string>
<string name="text_no_running_script">No running script</string>
<string name="text_already_stop_n_scripts">Stop %d script(s)</string>
<string name="text_no_file_rw_permission">No file r/w permission</string>
<string name="text_no_running_scripts">No running scripts</string>
<string name="text_already_stop_n_scripts">%d script(s) stopped</string>
<string name="text_requires_sdk_version_to_run_the_script">Required Android OS version:</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
@@ -14,7 +14,7 @@
<string name="text_no_floating_window_permission">No drawing overlay permission</string>
<string name="text_accessibility_service_description">Auto.js</string>
<string name="text_should_enable_key_observing">Key observing is disabled, please enable in settings</string>
<string name="no_write_settings_permissin">No writing settings permission</string>
<string name="no_write_settings_permission">No writing settings permission</string>
<string name="exception_notification_service_disabled">通知服务未运行,请重新启用通知权限</string>

View File

@@ -5,8 +5,8 @@
<string name="text_path_is_empty">路径为空</string>
<string name="text_file_not_exists">文件不存在</string>
<string name="text_no_file_rw_permission">无文件读写权限</string>
<string name="text_no_running_script">没有正在运行的脚本</string>
<string name="text_already_stop_n_scripts">已停止%d个正在运行的脚本</string>
<string name="text_no_running_scripts">没有正在运行的脚本</string>
<string name="text_already_stop_n_scripts">已停止 %d 个正在运行的脚本</string>
<string name="text_requires_sdk_version_to_run_the_script">本脚本需要此安卓版本以上才能运行:</string>
<string name="ok">确定</string>
<string name="cancel">取消</string>
@@ -15,7 +15,7 @@
<string name="text_accessibility_service_description">使脚本自动操作(点击、长按、滑动等)所需,若关闭则只能执行不涉及自动操作的脚本。</string>
<string name="text_should_enable_key_observing">按键监听未启用,请在软件设置中开启</string>
<string name="text_should_enable_gesture_observing">手势监听未启用,请在软件设置中开启</string>
<string name="no_write_settings_permissin">沒有修改系統设置权限</string>
<string name="no_write_settings_permission">沒有修改系統设置权限</string>
<string name="exception_notification_service_disabled">通知服务未运行,请重新启用通知权限</string>
<string name="text_requires_app_version_to_run_the_script" formatted="true">本脚本需要Auto.js版本号%d以上才能运行</string>
<string name="text_drawer_open">打开侧拉菜单</string>

View File

@@ -1,5 +1,5 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
@@ -27,7 +27,7 @@ android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
@@ -37,7 +37,7 @@ repositories {
}
dependencies {
testImplementation 'junit:junit:4.13.2'
api 'androidx.appcompat:appcompat:1.4.0'
api project(':common')
testImplementation "junit:junit:${junitVer}"
implementation "androidx.appcompat:appcompat:1.4.0"
api project(":common")
}

View File

@@ -28,7 +28,7 @@ class DfsFilterTest {
fun filter() {
val filter = RandomFilter()
val root = TestUiObject(10)
val list = DFS(filter).search(root)
val list = DFS.search(root, filter)
for (uiObject in list) {
if (root !== uiObject)
uiObject.recycle()

View File

@@ -4,25 +4,22 @@ import org.apache.groovy.json.internal.LazyMap
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.6.0"
ext.kotlinVer = "1.6.10"
ext.junitVer = "4.13.2"
repositories {
mavenCentral()
google()
maven { url "https://maven.aliyun.com/repository/apache-snapshots" }
maven { url "https://maven.aliyun.com/repository/central" }
maven { url "https://maven.aliyun.com/repository/google" }
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
maven { url "https://maven.aliyun.com/repository/grails-core" }
maven { url "https://maven.aliyun.com/repository/jcenter" }
maven { url "https://maven.aliyun.com/repository/public" }
maven { url "https://maven.aliyun.com/repository/spring" }
maven { url "https://maven.aliyun.com/repository/spring-plugin" }
maven { url "https://repo.huaweicloud.com/repository/maven" }
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0-alpha05'
classpath "com.android.tools.build:gradle:7.2.0-alpha06"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVer}"
classpath "com.jakewharton:butterknife-gradle-plugin:10.2.1"
classpath "org.codehaus.groovy:groovy-json:3.0.8"
@@ -34,15 +31,11 @@ allprojects {
maven { url "https://jitpack.io" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
mavenCentral()
maven { url "https://maven.aliyun.com/repository/apache-snapshots" }
maven { url "https://maven.aliyun.com/repository/central" }
maven { url "https://maven.aliyun.com/repository/google" }
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
maven { url "https://maven.aliyun.com/repository/grails-core" }
maven { url "https://maven.aliyun.com/repository/jcenter" }
maven { url "https://maven.aliyun.com/repository/public" }
maven { url "https://maven.aliyun.com/repository/spring" }
maven { url "https://maven.aliyun.com/repository/spring-plugin" }
maven { url "https://repo.huaweicloud.com/repository/maven" }
google()
}

View File

@@ -1,9 +1,9 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:deprecation'
}
// tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:deprecation"
// }
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
@@ -24,7 +24,7 @@ android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
lintOptions {
@@ -37,8 +37,33 @@ android {
}
dependencies {
testImplementation 'junit:junit:4.13.2'
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api 'androidx.annotation:annotation:1.3.0'
api 'com.github.hyb1996:settingscompat:1.1.5'
testImplementation "junit:junit:${junitVer}"
// Kotlin
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVer}"
// RoundedImageView
api "com.makeramen:roundedimageview:2.3.0"
// EventBus
api "org.greenrobot:eventbus:3.3.1"
// Annotation
api "androidx.annotation:annotation:1.3.0"
// Preference
api "androidx.preference:preference-ktx:1.1.1"
// RootShell
api "com.github.Stericson:RootShell:1.6"
// JDeferred
api "org.jdeferred:jdeferred-android-aar:1.2.6"
// Auto.js
api "com.github.hyb1996:settingscompat:1.1.5"
api "com.github.hyb1996:EnhancedFloaty:0.31"
// Rhino
api project(":libs:org.mozilla.rhino-1.7.14")
}

View File

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

View File

@@ -1,5 +1,6 @@
package com.stardust.app;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
@@ -20,24 +21,17 @@ public class DialogUtils {
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)
int type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
: 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();
}
});
GlobalAppContext.post(dialog::show);
}
return dialog;
}

View File

@@ -23,21 +23,19 @@ import java.util.Objects;
* Created by Stardust on 2017/4/1.
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
public class PFiles {
static final int DEFAULT_BUFFER_SIZE = 8192;
static final String DEFAULT_ENCODING = Charset.defaultCharset().name();
public static PFileInterface open(String path, String mode, String encoding, int bufferSize) {
switch (mode) {
case "r":
return new PReadableTextFile(path, encoding, bufferSize);
case "w":
return new PWritableTextFile(path, encoding, bufferSize, false);
case "a":
return new PWritableTextFile(path, encoding, bufferSize, true);
}
return null;
return switch (mode) {
case "r" -> new PReadableTextFile(path, encoding, bufferSize);
case "w" -> new PWritableTextFile(path, encoding, bufferSize, false);
case "a" -> new PWritableTextFile(path, encoding, bufferSize, true);
default -> null;
};
}
public static Object open(String path, String mode, String encoding) {

View File

@@ -107,16 +107,14 @@ public final class ResourceMonitor {
if (sHandler == null) {
sHandler = new Handler(Looper.getMainLooper());
}
sHandler.post(new Runnable() {
public final void run() {
UnclosedResourceDetectedException detectedException = new UnclosedResourceDetectedException(exception);
detectedException.fillInStackTrace();
Log.w(LOG_TAG, "UnclosedResourceDetected", detectedException);
if (sUnclosedResourceDetectedHandler != null) {
sUnclosedResourceDetectedHandler.onUnclosedResourceDetected(detectedException);
} else {
throw detectedException;
}
sHandler.post(() -> {
UnclosedResourceDetectedException detectedException = new UnclosedResourceDetectedException(exception);
detectedException.fillInStackTrace();
Log.w(LOG_TAG, "UnclosedResourceDetected", detectedException);
if (sUnclosedResourceDetectedHandler != null) {
sUnclosedResourceDetectedHandler.onUnclosedResourceDetected(detectedException);
} else {
throw detectedException;
}
});
}

View File

@@ -12,8 +12,11 @@
# org.gradle.parallel=true
#Thu Nov 11 16:38:18 CST 2021
org.gradle.jvmargs=-Xms2g -Xmx2g -Dkotlin.daemon.jvm.options\="-Xmx2g" -Dfile.encoding\=UTF-8
org.gradle.jvmargs=-Xms2g -Xmx2g -Dkotlin.daemon.jvm.options\="-Xmx2g" -Dfile.encoding\=UTF-8 -XX:+UseParallelGC
android.useAndroidX=true
android.enableJetifier=true
android.injected.studio.version.check=false
android.injected.studio.version.check=false
org.gradle.daemon=true
org.gradle.parallel=true

View File

@@ -1,6 +1,6 @@
#Fri Nov 26 16:35:39 CST 2021
#Thu Dec 30 20:07:37 CST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStoreBase=GRADLE_USER_HOME

View File

@@ -1,5 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: "com.android.application"
apply plugin: "kotlin-android"
android {
compileSdkVersion versions.compile
@@ -15,8 +15,8 @@ android {
}
lintOptions {
abortOnError false
disable 'MissingTranslation'
disable 'ExtraTranslation'
disable "MissingTranslation"
disable "ExtraTranslation"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_16
@@ -46,51 +46,51 @@ android {
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
def buildApkPluginForAbi(File pluginProjectDir, String abi) {
copy {
from file('..\\app\\release\\')
into new File(pluginProjectDir, 'app\\src\\main\\assets')
from file("..\\app\\release\\")
into new File(pluginProjectDir, "app\\src\\main\\assets")
def fileName = "inrt-" + abi + "-release.apk"
include fileName
rename fileName, 'template.apk'
rename fileName, "template.apk"
}
exec {
workingDir pluginProjectDir
commandLine 'gradlew.bat', 'assembleRelease'
commandLine "gradlew.bat", "assembleRelease"
}
copy {
from new File(pluginProjectDir, 'app\\build\\outputs\\apk\\release')
into file('..\\common\\release')
def fileName = '打包插件-' + versions.appVersionName + '-release.apk'
from new File(pluginProjectDir, "app\\build\\outputs\\apk\\release")
into file("..\\common\\release")
def fileName = "打包插件-" + versions.appVersionName + "-release.apk"
include fileName
rename fileName, '打包插件-' + abi + '-' + versions.appVersionName + '-release.apk'
rename fileName, "打包插件-" + abi + "-" + versions.appVersionName + "-release.apk"
}
}
task buildApkPlugin {
doLast {
def pluginProjectDirPath = '..\\..\\AutoJsApkBuilderPlugin'
def pluginProjectDirPath = "..\\..\\AutoJsApkBuilderPlugin"
def pluginProjectDir = file(pluginProjectDirPath)
if (!pluginProjectDir.exists() || !pluginProjectDir.isDirectory()) {
println 'pluginProjectDir not exists'
println "pluginProjectDir not exists"
return
}
buildApkPluginForAbi(pluginProjectDir, 'armeabi-v7a')
buildApkPluginForAbi(pluginProjectDir, "armeabi-v7a")
}
}
tasks.whenTaskAdded { task ->
if (task.name == 'assembleRelease') {
task.finalizedBy 'buildApkPlugin'
if (task.name == "assembleRelease") {
task.finalizedBy "buildApkPlugin"
}
}
@@ -105,14 +105,14 @@ repositories {
}
dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation "junit:junit:${junitVer}"
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation "com.github.bumptech.glide:glide:4.12.0"
implementation project(":libs:org.mozilla.rhino-1.7.14")
implementation project(":libs:org.opencv-4.5.4")
implementation project(':automator')
implementation project(':common')
implementation project(':autojs')
implementation project(":automator")
implementation project(":common")
implementation project(":autojs")
}

View File

@@ -19,6 +19,7 @@ import com.stardust.auojs.inrt.launch.GlobalProjectLauncher
import java.util.ArrayList
import android.content.pm.PackageManager.PERMISSION_DENIED
import android.os.Looper
/**
* Created by Stardust on 2018/2/2.
@@ -34,7 +35,7 @@ class SplashActivity : AppCompatActivity() {
if (!Pref.isFirstUsing) {
main()
} else {
Handler().postDelayed({ this@SplashActivity.main() }, INIT_TIMEOUT)
Handler(Looper.myLooper()!!).postDelayed({ this@SplashActivity.main() }, INIT_TIMEOUT)
}
}

View File

@@ -19,6 +19,6 @@
<string name="summary_not_show_main_activity">打开应用后直接运行脚本</string>
<string name="text_others">其他</string>
<string name="powered_by_autojs">Powered by Auto.js</string>
<string name="text_execution_finished" formatted="false">\n------------\n[%s]运行结束用时%f</string>
<string name="text_execution_finished" formatted="false">[%s] 运行结束 (用时 %f 秒)</string>
</resources>

View File

@@ -5,4 +5,4 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
}
configurations.maybeCreate("default")
artifacts.add("default", file("rhino-1.7.14-SNAPSHOT.jar"))
artifacts.add("default", file("rhino-1.7.14.jar"))

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{
"appVersionCode": 654,
"appVersionName": "6.0.0",
"appSinceDate": "Dec 1, 2021",
"appVersionCode": 694,
"appVersionName": "6.0.1",
"appSinceDate": "Jan 1, 2022",
"target": 28,
"mini": 24,
"compile": 31