Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ce51e4a01 | ||
|
|
ad5b7c9b37 | ||
|
|
8e8edbf848 | ||
|
|
98cf339cce |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -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/
|
||||
31
README.md
31
README.md
@@ -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](https://github.com/SuperMonster003/Auto.js-TypeScript-Declarations) { author: [SuperMonster003](https://github.com/SuperMonster003) })
|
||||
[//]: # ( - `Auto.js 声明文件 (.d.ts)`)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() + "{" +
|
||||
|
||||
138
app/src/main/java/org/autojs/autojs/pluginclient/Buffer.java
Normal file
138
app/src/main/java/org/autojs/autojs/pluginclient/Buffer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
283
app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java
Normal file
283
app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
81
app/src/main/java/org/autojs/autojs/tool/IOTool.java
Normal file
81
app/src/main/java/org/autojs/autojs/tool/IOTool.java
Normal 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 "";
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
32
app/src/main/java/org/autojs/autojs/tool/ThreadTool.java
Normal file
32
app/src/main/java/org/autojs/autojs/tool/ThreadTool.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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]Finished,spent %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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
})();
|
||||
|
||||
|
||||
}();
|
||||
27
autojs/src/main/assets/modules/__$base64__.js
Normal file
27
autojs/src/main/assets/modules/__$base64__.js
Normal 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;
|
||||
};
|
||||
@@ -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
29
autojs/src/main/assets/modules/custom-polyfill.js
Normal file
29
autojs/src/main/assets/modules/custom-polyfill.js
Normal 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);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 -> "";
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() + "]";
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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&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>
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
15
build.gradle
15
build.gradle
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
BIN
libs/org.mozilla.rhino-1.7.14/rhino-1.7.14.jar
Normal file
BIN
libs/org.mozilla.rhino-1.7.14/rhino-1.7.14.jar
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user