diff --git a/README.md b/README.md
index c566faff..ef14345d 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-
+
Android 平台支持无障碍服务的 JavaScript 自动化工具
@@ -16,7 +16,7 @@
-

+

@@ -104,6 +104,19 @@
[comment]: <> "Version history only shows last 3 versions"
+# v6.1.1
+
+###### 2022/05/31
+
+* `新增` 检查更新/下载更新/更新提示功能 (参阅 设置页面) (暂不支持安卓 7.x 系统)
+* `修复` 应用在安卓 10 系统无法读写外部存储的问题 _[`issue #17`](https://github.com/SuperMonster003/AutoJs6/issues/17)_
+* `修复` 编辑器页面长按时可能导致应用崩溃的问题 _[`issue #18`](https://github.com/SuperMonster003/AutoJs6/issues/18)_
+* `修复` 编辑器页面长按菜单 "删除行" 和 "复制行" 功能无效的问题
+* `修复` 编辑器页面选项菜单中 "粘贴" 功能缺失的问题
+* `优化` 部分异常消息字符串资源化 (en / zh)
+* `优化` 调整内容未保存对话框的按钮布局并增加颜色区分
+* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v611)_
+
# v6.1.0
###### 2022/05/26 - 包名变更 谨慎升级
@@ -150,7 +163,7 @@
* `优化` 保存 ImageWrapper 所有实例弱引用并在脚本结束时自动回收 (实验性)
* `优化` 附加 CircleImageView 版本 3.1.0
* `优化` 升级 Rhino 引擎版本 1.7.14 -> 1.7.15-snapshot
-* `优化` 部分依赖或本地库版本调整 (详见 [`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v610))
+* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v610)_
# v6.0.3
@@ -169,28 +182,13 @@
* `优化` 主页抽屉增加前台服务状态开关的同步
* `优化` 主页抽屉展开时立即按需同步开关状态
* `优化` 显示指针位置增加状态检测及结果提示
-* `优化` 支持 64 位操作系统 (Ref to TonyJiangWJ)
+* `优化` 支持 64 位操作系统 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ))
* `优化` 悬浮窗初始化时同时应用透明度设置 (无需点击后再应用透明度)
* `优化` 重置文件内容时增加是否为示例代码文件的检测并增加结果提示
* `优化` 转移打包插件下载地址 GitHub -> JsDelivr
* `优化` 附加 Zeugma Solutions LocaleHelper 版本 1.5.1
* `优化` 降级 Android Material 版本 1.6.0-alpha02 -> 1.5.0
-* `优化` 部分依赖或本地库版本调整 (详见 [`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v603))
-
-# v6.0.2
-
-###### 2022/02/05
-
-* `新增` images.bilateralFilter() 双边滤波图像处理方法
-* `修复` 多次调用 toast 只生效最后一次调用的问题
-* `修复` toast.dismiss() 可能无效的问题
-* `修复` 客户端模式及服务端模式开关可能无法正常工作的问题
-* `修复` 客户端模式及服务端模式开关状态不能正常刷新的问题
-* `修复` Android 7 解析 UI 模式 text 元素异常 (Ref to TonyJiangWJ) _[`issue #4`](https://github.com/SuperMonster003/AutoJs6/issues/4)_ _[`#9`](https://github.com/SuperMonster003/AutoJs6/issues/9)_
-* `优化` 忽略 sleep() 的 ScriptInterruptedException 异常
-* `优化` 附加 Androidx AppCompat (Legacy) 版本 1.0.2
-* `优化` 升级 Rhino 引擎版本 1.7.14-snapshot -> 1.7.14
-* `优化` 部分依赖或本地库版本调整 (详见 [`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v602))
+* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](https://github.com/SuperMonster003/AutoJs6/blob/master/app/src/main/assets/doc/CHANGELOG.md#v603)_
##### 更多版本历史可参阅
diff --git a/app/build.gradle b/app/build.gradle
index de389287..277e68ab 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -62,7 +62,7 @@ dependencies {
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
- implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
+ implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
// Glide
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2c542d96..ba22db17 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,10 +2,25 @@
xmlns:tools="http://schemas.android.com/tools"
package="org.autojs.autojs">
+
+
+ android:maxSdkVersion="29" />
@@ -42,6 +57,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
+ android:requestLegacyExternalStorage="true"
tools:replace="android:label, android:allowBackup"
tools:ignore="DataExtractionRules">
diff --git a/app/src/main/assets/doc/CHANGELOG.md b/app/src/main/assets/doc/CHANGELOG.md
index d2d633fa..e151c18d 100644
--- a/app/src/main/assets/doc/CHANGELOG.md
+++ b/app/src/main/assets/doc/CHANGELOG.md
@@ -4,6 +4,20 @@
******
+# v6.1.1
+
+###### 2022/05/31
+
+* `新增` 检查更新/下载更新/更新提示功能 (参阅 设置页面) (暂不支持安卓 7.x 系统)
+* `修复` 应用在安卓 10 系统无法读写外部存储的问题 _[`issue #17`](https://github.com/SuperMonster003/AutoJs6/issues/17)_
+* `修复` 编辑器页面长按时可能导致应用崩溃的问题 _[`issue #18`](https://github.com/SuperMonster003/AutoJs6/issues/18)_
+* `修复` 编辑器页面长按菜单 "删除行" 和 "复制行" 功能无效的问题
+* `修复` 编辑器页面选项菜单中 "粘贴" 功能缺失的问题
+* `优化` 部分异常消息字符串资源化 (en / zh)
+* `优化` 调整内容未保存对话框的按钮布局并增加颜色区分
+* `优化` 附加 github-api 版本 1.306
+* `优化` 替换 retrofit2-rxjava2-adapter 版本 1.0.0 为 adapter-rxjava2 版本 2.9.0
+
# v6.1.0
###### 2022/05/26 - 包名变更 谨慎升级
@@ -82,14 +96,14 @@
* `优化` 主页抽屉增加前台服务状态开关的同步
* `优化` 主页抽屉展开时立即按需同步开关状态
* `优化` 显示指针位置增加状态检测及结果提示
-* `优化` 支持 64 位操作系统 (Ref to TonyJiangWJ)
+* `优化` 支持 64 位操作系统 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ))
* `优化` 悬浮窗初始化时同时应用透明度设置 (无需点击后再应用透明度)
* `优化` 重置文件内容时增加是否为示例代码文件的检测并增加结果提示
* `优化` 转移打包插件下载地址 GitHub -> JsDelivr
* `优化` 附加 Zeugma Solutions LocaleHelper 版本 1.5.1
* `优化` 降级 Android Material 版本 1.6.0-alpha02 -> 1.5.0
* `优化` 升级 Kotlinx Coroutines 版本 1.6.0-native-mt -> 1.6.0
-* `优化` 升级 OpenCV 版本 3.4.3 -> 4.5.4 -> 4.5.5 (Ref to TonyJiangWJ)
+* `优化` 升级 OpenCV 版本 3.4.3 -> 4.5.4 -> 4.5.5 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ))
* `优化` 升级 Okhttp3 版本 3.10.0 -> 5.0.0-alpha.4 -> 5.0.0-alpha.6
* `优化` 升级 Android Gradle 插件版本 7.2.0-beta01 -> 7.3.0-alpha06
* `优化` 升级 Auto.js-ApkBuilder 版本 1.0.1 -> 1.0.3
@@ -107,7 +121,7 @@
* `修复` toast.dismiss() 可能无效的问题
* `修复` 客户端模式及服务端模式开关可能无法正常工作的问题
* `修复` 客户端模式及服务端模式开关状态不能正常刷新的问题
-* `修复` Android 7 解析 UI 模式 text 元素异常 (Ref to TonyJiangWJ) _[`issue #4`](https://github.com/SuperMonster003/AutoJs6/issues/4)_ _[`#9`](https://github.com/SuperMonster003/AutoJs6/issues/9)_
+* `修复` Android 7 解析 UI 模式 text 元素异常 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ)) _[`issue #4`](https://github.com/SuperMonster003/AutoJs6/issues/4)_ _[`#9`](https://github.com/SuperMonster003/AutoJs6/issues/9)_
* `优化` 忽略 sleep() 的 ScriptInterruptedException 异常
* `优化` 附加 Androidx AppCompat (Legacy) 版本 1.0.2
* `优化` 升级 Androidx AppCompat 版本 1.4.0 -> 1.4.1
@@ -122,8 +136,8 @@
###### 2022/01/01
-* `新增` 连接 VSCode 插件支持客户端 (LAN) 及服务端 (LAN/ADB) 方式 (Ref to Auto.js Pro)
-* `新增` 内置 base64 模块 (Ref to Auto.js Pro)
+* `新增` 连接 VSCode 插件支持客户端 (LAN) 及服务端 (LAN/ADB) 方式 (Ref to [Auto.js Pro](https://g.pro.autojs.org/))
+* `新增` 内置 base64 模块 (Ref to [Auto.js Pro](https://g.pro.autojs.org/))
* `新增` 增加 isInteger/isNullish/isPlainObject/isPrimitive/isReference 全局方法
* `新增` 增加 polyfill (Object.getOwnPropertyDescriptors)
* `新增` 增加 polyfill (Array.prototype.flat)
@@ -159,7 +173,7 @@
* `优化` 升级 JSON 模块至 2017-06-12 版本并整合 cycle.js
* `优化` 移除 Activity 前置时的自动检查更新功能并移除检查更新相关按钮
* `优化` AppOpsKt#isOpPermissionGranted 内部代码逻辑
-* `优化` ResourceMonitor 使用 ReentrantLock 增强安全性 (Ref to TonyJiangWJ)
+* `优化` ResourceMonitor 使用 ReentrantLock 增强安全性 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ))
* `优化` 使用 Maven Central 等仓库替换 JCenter 仓库
* `优化` 抽离并移除重复的本地库文件
* `优化` 本地化 CrashReport 版本 2.6.6
diff --git a/app/src/main/assets/sample/工具/摩斯电码 [v6.1.0+].js b/app/src/main/assets/sample/工具/摩斯电码 [v6.1.0+].js
index 1d0cceb9..b0c7e212 100644
--- a/app/src/main/assets/sample/工具/摩斯电码 [v6.1.0+].js
+++ b/app/src/main/assets/sample/工具/摩斯电码 [v6.1.0+].js
@@ -17,7 +17,7 @@ console.log('code: ' + morse.code);
console.log('pattern: ' + morse.pattern);
// Call vibrate() if you need your device play this morse code by vibration.
-// zh-CN: Call vibrate() if you need your device play this morse code by vibration.
+// zh-CN: 可调用 vibrate() 方法使设备按摩斯电码模式震动.
// morse.vibrate();
diff --git a/app/src/main/java/com/stardust/theme/preference/ThemeColorPreferenceCategory.java b/app/src/main/java/com/stardust/theme/preference/ThemeColorPreferenceCategory.java
index 84b0430f..409240e6 100644
--- a/app/src/main/java/com/stardust/theme/preference/ThemeColorPreferenceCategory.java
+++ b/app/src/main/java/com/stardust/theme/preference/ThemeColorPreferenceCategory.java
@@ -2,6 +2,7 @@ package com.stardust.theme.preference;
import android.content.Context;
import android.graphics.Color;
+import android.graphics.Typeface;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
import android.view.View;
@@ -54,8 +55,10 @@ public class ThemeColorPreferenceCategory extends PreferenceCategory implements
public View onCreateView(ViewGroup parent) {
View view = super.onCreateView(parent);
mTitleTextView = view.findViewById(android.R.id.title);
- if (mColor != Color.TRANSPARENT)
+ if (mColor != Color.TRANSPARENT) {
mTitleTextView.setTextColor(mColor);
+ }
+ mTitleTextView.setTypeface(null, Typeface.BOLD);
return view;
}
diff --git a/app/src/main/java/org/autojs/autojs/Pref.java b/app/src/main/java/org/autojs/autojs/Pref.java
index 5d151488..6ab196fe 100644
--- a/app/src/main/java/org/autojs/autojs/Pref.java
+++ b/app/src/main/java/org/autojs/autojs/Pref.java
@@ -3,6 +3,7 @@ package org.autojs.autojs;
import android.content.SharedPreferences;
import android.os.Environment;
+import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.stardust.app.GlobalAppContext;
@@ -10,6 +11,9 @@ import com.stardust.autojs.runtime.accessibility.AccessibilityConfig;
import org.autojs.autojs.autojs.key.GlobalKeyObserver;
import org.autojs.autojs6.R;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
import java.io.File;
import java.util.Objects;
@@ -21,6 +25,7 @@ public class Pref {
private static final String KEY_SERVER_ADDRESS = "KEY_SERVER_ADDRESS";
private static final String KEY_FLOATING_MENU_SHOWN = "KEY_FLOATING_MENU_SHOWN";
+ private static final String KEY_LAST_UPDATED_CHECKED = "KEY_LAST_UPDATED_CHECKED";
private static final String KEY_APP_LANG_INDEX = "KEY_APP_LANG_INDEX";
private static final String KEY_EDITOR_THEME = "editor.theme";
private static final String KEY_EDITOR_TEXT_SIZE = "editor.textSize";
@@ -51,6 +56,29 @@ public class Pref {
return def().getBoolean(getString(R.string.key_use_volume_control_running), true);
}
+ public static boolean isAutoCheckForUpdatesEnabled() {
+ return def().getBoolean(getString(R.string.key_auto_check_for_updates), true);
+ }
+
+ public static void refreshLastUpdatesCheckedTimestamp() {
+ def().edit().putLong(KEY_LAST_UPDATED_CHECKED, System.currentTimeMillis()).apply();
+ }
+
+ public static long getLastUpdatesCheckedTimestamp() {
+ return def().getLong(KEY_LAST_UPDATED_CHECKED, -1);
+ }
+
+ @Nullable
+ public static String getLastUpdatesCheckedTimeString() {
+ long ts = getLastUpdatesCheckedTimestamp();
+ if (ts < 0) {
+ return null;
+ }
+ DateTime dt = new DateTime(ts);
+ DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm");
+ return fmt.print(dt);
+ }
+
private static String getString(int id) {
return GlobalAppContext.getString(id);
}
diff --git a/app/src/main/java/org/autojs/autojs/build/TinySign.java b/app/src/main/java/org/autojs/autojs/build/TinySign.java
index 82e3cd88..6f56fd1a 100644
--- a/app/src/main/java/org/autojs/autojs/build/TinySign.java
+++ b/app/src/main/java/org/autojs/autojs/build/TinySign.java
@@ -39,10 +39,10 @@ public class TinySign {
private static void doDir(String prefix, File dir, ZipOutputStream zos, DigestOutputStream dos, Manifest m) throws IOException {
zos.putNextEntry(new ZipEntry(prefix));
zos.closeEntry();
- File[] arr$ = dir.listFiles();
+ File[] files = dir.listFiles();
- if (arr$ != null) {
- for (File f : arr$) {
+ if (files != null) {
+ for (File f : files) {
if (f.isFile()) {
doFile(prefix + f.getName(), f, zos, dos, m);
} else {
@@ -72,13 +72,8 @@ public class TinySign {
private static Manifest generateSF(Manifest manifest) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA1");
PrintStream print = new PrintStream(new DigestOutputStream(new OutputStream() {
- public void write(byte[] arg0) {
- }
-
- public void write(byte[] arg0, int arg1, int arg2) {
- }
-
- public void write(int arg0) {
+ @Override
+ public void write(int b) {
}
}, md), true, "UTF-8");
Manifest sf = new Manifest();
@@ -164,10 +159,10 @@ public class TinySign {
}
private static void zipAndSha1(File dir, ZipOutputStream zos, DigestOutputStream dos, Manifest m) throws IOException {
- File[] arr$ = dir.listFiles();
+ File[] files = dir.listFiles();
- if (arr$ != null) {
- for (File f : arr$) {
+ if (files != null) {
+ for (File f : files) {
if (!f.getName().startsWith("META-INF")) {
if (f.isFile()) {
doFile(f.getName(), f, zos, dos, m);
@@ -191,8 +186,8 @@ public class TinySign {
public void write(byte[] buffer) throws IOException {
try {
this.mSignature.update(buffer);
- } catch (SignatureException var3) {
- throw new IOException("SignatureException: " + var3);
+ } catch (SignatureException e) {
+ throw new IOException("SignatureException: " + e);
}
this.out.write(buffer);
@@ -201,8 +196,8 @@ public class TinySign {
public void write(byte[] b, int off, int len) throws IOException {
try {
this.mSignature.update(b, off, len);
- } catch (SignatureException var5) {
- throw new IOException("SignatureException: " + var5);
+ } catch (SignatureException e) {
+ throw new IOException("SignatureException: " + e);
}
this.out.write(b, off, len);
@@ -211,8 +206,8 @@ public class TinySign {
public void write(int b) throws IOException {
try {
this.mSignature.update((byte) b);
- } catch (SignatureException var3) {
- throw new IOException("SignatureException: " + var3);
+ } catch (SignatureException e) {
+ throw new IOException("SignatureException: " + e);
}
this.out.write(b);
diff --git a/app/src/main/java/org/autojs/autojs/external/shortcut/Shortcut.java b/app/src/main/java/org/autojs/autojs/external/shortcut/Shortcut.java
index 23596a87..bc47c0a9 100644
--- a/app/src/main/java/org/autojs/autojs/external/shortcut/Shortcut.java
+++ b/app/src/main/java/org/autojs/autojs/external/shortcut/Shortcut.java
@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
import android.os.Bundle;
import org.autojs.autojs.tool.BitmapTool;
+import org.autojs.autojs6.R;
/**
* Created by Stardust on 2017/1/20.
@@ -54,7 +55,7 @@ public class Shortcut {
public Shortcut iconRes(Intent.ShortcutIconResource icon) {
if (mIcon != null) {
- throw new IllegalStateException("Cannot set both iconRes and icon");
+ throw new IllegalStateException(mContext.getString(R.string.error_set_both_icon_res_and_icon));
}
mIconRes = icon;
return this;
@@ -67,7 +68,7 @@ public class Shortcut {
public Shortcut icon(Bitmap icon) {
if (mIconRes != null) {
- throw new IllegalStateException("Cannot set both iconRes and icon");
+ throw new IllegalStateException(mContext.getString(R.string.error_set_both_icon_res_and_icon));
}
if (icon.getByteCount() > 1024 * 500) {
mIcon = BitmapTool.scaleBitmap(icon, 200, 200);
diff --git a/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerChangeEvent.java b/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerChangeEvent.java
index 756b9848..11d2d0be 100644
--- a/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerChangeEvent.java
+++ b/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerChangeEvent.java
@@ -2,6 +2,10 @@ package org.autojs.autojs.model.explorer;
import androidx.annotation.NonNull;
+import com.stardust.app.GlobalAppContext;
+
+import org.autojs.autojs6.R;
+
public class ExplorerChangeEvent {
@@ -73,6 +77,6 @@ public class ExplorerChangeEvent {
case CHILDREN_CHANGE:
return "CHILDREN_CHANGE";
}
- throw new IllegalArgumentException("action = " + action);
+ throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "action", action));
}
}
diff --git a/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerFileItem.java b/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerFileItem.java
index 4732e66a..b44b581d 100644
--- a/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerFileItem.java
+++ b/app/src/main/java/org/autojs/autojs/model/explorer/ExplorerFileItem.java
@@ -2,11 +2,12 @@ package org.autojs.autojs.model.explorer;
import androidx.annotation.NonNull;
+import com.stardust.app.GlobalAppContext;
import com.stardust.pio.PFile;
-import com.stardust.util.ObjectHelper;
import com.stardust.util.Objects;
import org.autojs.autojs.model.script.ScriptFile;
+import org.autojs.autojs6.R;
import java.io.File;
import java.util.Arrays;
@@ -24,7 +25,11 @@ public class ExplorerFileItem implements ExplorerItem {
private final ExplorerPage mParent;
public ExplorerFileItem(PFile file, ExplorerPage parent) {
- ObjectHelper.requireNonNull(file, "file");
+ if (file == null) {
+ throw new NullPointerException(GlobalAppContext
+ .getString(R.string.error_method_called_with_null_argument,
+ "ExplorerFileItem.constructor", "file"));
+ }
mFile = file;
mParent = parent;
}
diff --git a/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java b/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java
new file mode 100644
index 00000000..3bc03b43
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java
@@ -0,0 +1,597 @@
+package org.autojs.autojs.network;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.Html;
+import android.text.Spanned;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.afollestad.materialdialogs.DialogAction;
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.afollestad.materialdialogs.internal.MDButton;
+import com.google.android.material.snackbar.BaseTransientBottomBar;
+import com.google.gson.GsonBuilder;
+import com.stardust.app.GlobalAppContext;
+import com.stardust.autojs.core.ui.widget.CustomSnackbar;
+import com.stardust.util.IntentUtil;
+
+import org.autojs.autojs.Pref;
+import org.autojs.autojs.external.fileprovider.AppFileProvider;
+import org.autojs.autojs.network.api.UpdateCheckerApi;
+import org.autojs.autojs.network.download.DownloadManager;
+import org.autojs.autojs.network.entity.VersionInfo;
+import org.autojs.autojs.tool.SimpleObserver;
+import org.autojs.autojs6.R;
+import org.kohsuke.github.GHAsset;
+import org.kohsuke.github.GHRelease;
+import org.kohsuke.github.GHRepository;
+import org.kohsuke.github.GHUser;
+import org.kohsuke.github.GitHub;
+import org.kohsuke.github.MarkdownMode;
+import org.kohsuke.github.PagedIterable;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.Reader;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.functions.Consumer;
+import io.reactivex.schedulers.Schedulers;
+import okhttp3.ResponseBody;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+/**
+ * Created by Stardust on 2017/9/20.
+ * Modified by SuperMonster003 as of Feb 26, 2022.
+ */
+
+public class UpdateChecker {
+
+ private MaterialDialog mUpdateDialog;
+ private MaterialDialog mPendingDialog;
+
+ public enum PromptMode {NONE, DIALOG, SNACKBAR}
+
+ private static final String TAG = UpdateChecker.class.getSimpleName();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Context mContext;
+ private final View mView;
+ private final String mBaseUrl;
+ private final String mUrl;
+ private final PromptMode mPromptMode;
+ private final SimpleObserver
mCallback;
+ private final Executor mGitHubExecutor = Executors.newSingleThreadExecutor();
+
+ private GitHub mGitHubConnection;
+ private UpdateCheckerApi checkerApi;
+
+ private UpdateChecker(Context context, View view, String baseUrl, String url, PromptMode promptMode, SimpleObserver callback) {
+ mContext = context;
+ mView = view;
+ mBaseUrl = baseUrl;
+ mUrl = url;
+ mPromptMode = promptMode;
+ mCallback = callback;
+ }
+
+ public void checkNow() {
+ mPendingDialog = getPendingDialog(mContext, R.string.text_checking_update);
+
+ if (mPromptMode == PromptMode.DIALOG) {
+ mPendingDialog.show();
+ }
+
+ getCheckerApi()
+ .checkForUpdates(mUrl)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new SimpleObserver<>() {
+ @Override
+ public void onNext(ResponseBody responseBody) {
+ if (mPendingDialog.isShowing()) {
+ mPendingDialog.dismiss();
+ }
+ Pref.refreshLastUpdatesCheckedTimestamp();
+
+ if (mCallback != null) {
+ mCallback.onNext(responseBody);
+ return;
+ }
+
+ try {
+ Properties prop = new Properties();
+ prop.load(responseBody.byteStream());
+
+ String versionNameKey = mContext.getString(R.string.key_app_property_version_name);
+ String versionName = prop.getProperty(versionNameKey);
+
+ String versionCodeKey = mContext.getString(R.string.key_app_property_version_code);
+ int versionCode = Integer.parseInt(prop.getProperty(versionCodeKey));
+
+ VersionInfo versionInfo = new VersionInfo(versionName, versionCode);
+
+ switch (mPromptMode) {
+ case DIALOG -> {
+ if (versionInfo.isNewer()) {
+ showDialog(versionInfo);
+ } else {
+ GlobalAppContext.toast(R.string.text_is_latest_version);
+ }
+ }
+ case SNACKBAR -> {
+ if (versionInfo.isNewer()) {
+ showSnackBar(versionInfo);
+ }
+ }
+ default -> throw new IllegalStateException(mContext.getString(
+ R.string.error_illegal_argument,
+ "promptMode", mPromptMode));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ GlobalAppContext.toast(mContext.getString(R.string.error_failed_to_parse_version_info));
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ e.printStackTrace();
+
+ if (mPendingDialog.isShowing()) {
+ mPendingDialog.dismiss();
+ }
+
+ if (mCallback != null) {
+ mCallback.onError(e);
+ return;
+ }
+
+ if (mPromptMode == PromptMode.DIALOG) {
+ new MaterialDialog.Builder(mContext)
+ .title(R.string.error_check_for_update)
+ .content(e.toString())
+ .positiveText(R.string.text_cancel)
+ .canceledOnTouchOutside(false)
+ .build()
+ .show();
+ }
+ }
+ });
+ }
+
+ private void showDialog(@NonNull VersionInfo versionInfo) {
+ showDialog(versionInfo, null);
+ }
+
+ private void showDialog(@NonNull VersionInfo versionInfo, Context context) {
+ // TODO by SuperMonster003 on May 30, 2022.
+ // ! A. Updates ignorance (and settings).
+ // ! B. VersionInfo: *.properties / *.json.
+
+ Context ctx = context != null ? context : mContext;
+
+ String propVersion = versionInfo.getVersionName();
+ if (propVersion == null) {
+ new MaterialDialog.Builder(ctx)
+ .title(R.string.error_check_for_update)
+ .content(R.string.error_parse_version_info)
+ .positiveText(R.string.dialog_button_back)
+ .build()
+ .show();
+ return;
+ }
+
+ mUpdateDialog = new MaterialDialog.Builder(ctx)
+ .title(propVersion)
+ .content(R.string.text_getting_release_notes)
+ .neutralText(R.string.dialog_button_ignore_current_update)
+ .neutralColor(ctx.getColor(R.color.dialog_button_warn))
+ .negativeText(R.string.dialog_button_back)
+ .negativeColor(ctx.getColor(R.color.dialog_button_default))
+ .positiveText(R.string.dialog_button_update_now)
+ .positiveColor(ctx.getColor(R.color.dialog_button_unavailable))
+ .autoDismiss(false)
+ .cancelable(false)
+ .build();
+
+ mPendingDialog = getPendingDialog(ctx, R.string.text_preparing);
+
+ MDButton neutralButton = mUpdateDialog.getActionButton(DialogAction.NEUTRAL);
+ neutralButton.setOnClickListener(v -> {
+ // TODO by SuperMonster003 on May 30, 2022.
+ // ! Updates ignorance.
+
+ new MaterialDialog.Builder(ctx)
+ .title(R.string.text_prompt)
+ .content(R.string.text_under_development_content)
+ .positiveText(R.string.dialog_button_back)
+ .build()
+ .show();
+ });
+
+ MDButton negativeButton = mUpdateDialog.getActionButton(DialogAction.NEGATIVE);
+ negativeButton.setOnClickListener(v -> mUpdateDialog.dismiss());
+
+ MDButton positiveButton = mUpdateDialog.getActionButton(DialogAction.POSITIVE);
+ positiveButton.setOnClickListener(null);
+
+ mUpdateDialog.show();
+
+ // TODO by SuperMonster003 on May 31, 2022.
+ // ! Android 7.x (N and N_MR1) compatibility.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ mUpdateDialog.dismiss();
+ new MaterialDialog.Builder(ctx)
+ .title(R.string.error_check_for_update)
+ .content(R.string.error_sdk_lower_than_o_not_supported_yet)
+ .positiveText(R.string.text_cancel)
+ .cancelable(false)
+ .build()
+ .show();
+ return;
+ }
+
+ mGitHubExecutor.execute(() -> {
+ GitHub github = connectToGitHubIfNeeded();
+ if (github == null) {
+ setDialogContent(mUpdateDialog, R.string.error_cannot_connect_to_github);
+ return;
+ }
+
+ String userName = ctx.getString(R.string.developer_full_name);
+ String repoName = ctx.getString(R.string.app_name);
+ GHRepository repo = getGitHubRepo(github, userName, repoName);
+ if (repo == null) {
+ setDialogContent(mUpdateDialog, ctx.getString(R.string.error_invalid_github_repo, repoName));
+ return;
+ }
+
+ GHRelease release = getGitHubRelease(repo);
+ if (release == null) {
+ setDialogContent(mUpdateDialog, R.string.error_get_github_latest_release);
+ return;
+ }
+
+ String releaseTag = release.getTagName();
+ if (!checkReleaseTagMatchesProp(releaseTag, propVersion)) {
+ setDialogContent(mUpdateDialog, R.string.error_corresponding_github_release_may_not_published);
+ return;
+ }
+
+ String rawHtmlContent = getRawHtmlFromRelease(repo, release);
+ Spanned htmlContent = Html.fromHtml(rawHtmlContent, Html.FROM_HTML_MODE_COMPACT);
+ if (htmlContent.toString().isEmpty()) {
+ setDialogContent(mUpdateDialog, R.string.text_empty_release_note);
+ } else {
+ setDialogContent(mUpdateDialog, htmlContent);
+ }
+
+ PagedIterable assets = getGitHubAssets(release);
+ if (assets == null) {
+ setDialogContent(mUpdateDialog, R.string.error_empty_github_release_assets);
+ return;
+ }
+
+ setDialogUpdateButton(ctx, assets, versionInfo);
+ });
+ }
+
+ @Nullable
+ private PagedIterable getGitHubAssets(GHRelease release) {
+ try {
+ PagedIterable assets = release.listAssets();
+ if (assets.toArray().length > 0) {
+ return assets;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @SuppressLint("CheckResult")
+ private void download(Context ctx, String downloadUrl, String fileName, VersionInfo versionInfo) {
+ File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ final String path = new File(downloadDir, fileName).getPath();
+
+ Consumer onNext = file -> {
+ mUpdateDialog = null;
+ IntentUtil.installApkOrToast(ctx, file.getPath(), AppFileProvider.AUTHORITY);
+ };
+
+ Consumer onError = e -> {
+ Log.d(TAG, "onError: printing stack trace");
+ e.printStackTrace();
+
+ String msg = e.getMessage();
+
+ MaterialDialog d = new MaterialDialog.Builder(ctx)
+ .title(R.string.text_download_failed)
+ .content(msg == null ? ctx.getString(R.string.error_unknown) : msg)
+ .negativeText(R.string.text_cancel)
+ .negativeColor(ctx.getColor(R.color.dialog_button_default))
+ .onNegative((dialog, which) -> {
+ dialog.dismiss();
+ mUpdateDialog = null;
+ })
+ .positiveText("")
+ .positiveColor(ctx.getColor(R.color.dialog_button_failure))
+ .autoDismiss(false)
+ .cancelable(false)
+ .build();
+
+ if (mPendingDialog != null) {
+ mPendingDialog.dismiss();
+ }
+
+ if (mUpdateDialog != null) {
+ d.getActionButton(DialogAction.NEGATIVE).setText(R.string.dialog_button_quit);
+ d.getActionButton(DialogAction.NEGATIVE).setTextColor(ctx.getColor(R.color.dialog_button_caution));
+ d.getActionButton(DialogAction.POSITIVE).setText(R.string.dialog_button_retry);
+ d.getActionButton(DialogAction.POSITIVE).setOnClickListener(v -> {
+ d.dismiss();
+ mUpdateDialog.show();
+ });
+ }
+
+ d.show();
+ };
+
+ DownloadManager.getInstance().downloadWithProgress(ctx, downloadUrl, path, versionInfo)
+ .subscribeOn(AndroidSchedulers.mainThread())
+ .subscribe(onNext, onError);
+ }
+
+ private void setDialogUpdateButton(@NonNull Context ctx, PagedIterable ghAssets, VersionInfo versionInfo) {
+ MDButton positiveButton = mUpdateDialog.getActionButton(DialogAction.POSITIVE);
+ positiveButton.setTextColor(ctx.getColor(R.color.dialog_button_attraction));
+ positiveButton.setOnClickListener(v -> {
+ if (!mUpdateDialog.isShowing()) {
+ return;
+ }
+ mUpdateDialog.dismiss();
+ mPendingDialog.show();
+
+ mGitHubExecutor.execute(() -> {
+ String[] abiList = Build.SUPPORTED_ABIS;
+ String abiBackup = "universal";
+
+ String url = null;
+ String urlBackup = null;
+ String fileName = null;
+ String fileNameBackup = null;
+
+ try {
+ assets:
+ for (GHAsset ghAsset : ghAssets) {
+ String assetName = ghAsset.getName();
+ String assetUrl = ghAsset.getBrowserDownloadUrl();
+ for (String abi : abiList) {
+ String regex = ".*\\b" + abi + "\\b.*";
+ if (assetName.matches(regex)) {
+ url = assetUrl;
+ fileName = assetName;
+ break assets;
+ }
+ }
+ if (assetName.contains(abiBackup)) {
+ urlBackup = assetUrl;
+ fileNameBackup = assetName;
+ }
+ }
+ } catch (Exception ignore) {
+ // Ignored.
+ }
+
+ mPendingDialog.dismiss();
+
+ if (url == null && urlBackup == null) {
+ mHandler.post(() -> new MaterialDialog.Builder(ctx)
+ .content(R.string.error_cannot_get_download_url)
+ .positiveText(R.string.text_cancel)
+ .positiveColor(ctx.getColor(R.color.dialog_button_default))
+ .canceledOnTouchOutside(false)
+ .build()
+ .show());
+ return;
+ }
+
+ if (url != null) {
+ downloadWithHandler(ctx, url, fileName, versionInfo);
+ } else {
+ GlobalAppContext.toast(R.string.text_github_backup_url_used, Toast.LENGTH_LONG);
+ downloadWithHandler(ctx, urlBackup, fileNameBackup, versionInfo);
+ }
+ });
+ });
+ }
+
+ private void downloadWithHandler(Context ctx, String url, String fileName, VersionInfo versionInfo) {
+ mHandler.post(() -> download(ctx, url, fileName, versionInfo));
+ }
+
+ private String getRawHtmlFromRelease(GHRepository repo, GHRelease release) {
+ Reader reader;
+ try {
+ reader = repo.renderMarkdown(release.getBody(), MarkdownMode.MARKDOWN);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ StringBuilder textBuilder = new StringBuilder();
+ try (Reader r = new BufferedReader(reader)) {
+ int c;
+ while ((c = r.read()) != -1) {
+ textBuilder.append((char) c);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ return textBuilder.toString();
+ }
+
+ private GHRelease getGitHubRelease(GHRepository repo) {
+ try {
+ return repo.getLatestRelease();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private GHRepository getGitHubRepo(GitHub github, String userName, String repoName) {
+ try {
+ GHUser user = github.getUser(userName);
+ return user.getRepository(repoName);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private boolean checkReleaseTagMatchesProp(String releaseTag, String propVersion) {
+ return releaseTag.endsWith(propVersion);
+ }
+
+ private void setDialogContent(MaterialDialog dialog, String content) {
+ mHandler.post(() -> dialog.setContent(content));
+ }
+
+ private void setDialogContent(MaterialDialog dialog, Spanned content) {
+ mHandler.post(() -> dialog.setContent(content));
+ }
+
+ private void setDialogContent(MaterialDialog dialog, int resId) {
+ mHandler.post(() -> dialog.setContent(resId));
+ }
+
+ private GitHub connectToGitHubIfNeeded() {
+ try {
+ if (mGitHubConnection == null) {
+ // FIXME by SuperMonster003 on May 31, 2022.
+ // ! Given that java.time package was added only in Android O (API 26),
+ // ! API 24 and 25 will throw an java.lang.NoClassDefFoundError here.
+ mGitHubConnection = GitHub.connectAnonymously();
+ }
+ return mGitHubConnection;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private void showSnackBar(@NonNull VersionInfo versionInfo) {
+ String content = mContext.getString(R.string.text_new_version_found) + ": " + versionInfo.getVersionName();
+
+ CustomSnackbar snackbar = CustomSnackbar.make(mView, content, BaseTransientBottomBar.LENGTH_INDEFINITE);
+ snackbar.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
+ .setActionOne(R.string.text_updates_snack_bar_act_view, v -> showDialog(versionInfo, mView.getContext()))
+ .setActionTwo(R.string.text_updates_snack_bar_act_later, null)
+ .show();
+ }
+
+ private MaterialDialog getPendingDialog(Context context, int content) {
+ // TODO by SuperMonster003 on May 29, 2022.
+ // ! Add a "CANCEL" button for interruption.
+ // ! Concurrent programming may be needed.
+
+ return new MaterialDialog.Builder(context)
+ .content(content)
+ .autoDismiss(false)
+ .cancelable(false)
+ .build();
+ }
+
+ public static class Builder {
+ private final Context mContext;
+
+ private View mView;
+ private String mBaseUrl;
+ private String mUrl;
+ private PromptMode mPromptMode = PromptMode.NONE;
+ private SimpleObserver mCallback;
+
+ public Builder() {
+ this(GlobalAppContext.get());
+ }
+
+ public Builder(Context context) {
+ mContext = context;
+ }
+
+ public Builder(View view) {
+ this();
+ mView = view;
+ }
+
+ public Builder setBaseUrl(String baseUrl) {
+ this.mBaseUrl = baseUrl;
+ return this;
+ }
+
+ public Builder setUrl(String url) {
+ this.mUrl = url;
+ return this;
+ }
+
+ public Builder setPromptMode(PromptMode promptMode) {
+ this.mPromptMode = promptMode;
+ return this;
+ }
+
+ public Builder setCallback(SimpleObserver callback) {
+ this.mCallback = callback;
+ return this;
+ }
+
+ public UpdateChecker build() {
+ ensureUrl();
+ return new UpdateChecker(mContext, mView, mBaseUrl, mUrl, mPromptMode, mCallback);
+ }
+
+ private void ensureUrl() {
+ String regexUrl = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
+ if (mBaseUrl != null) {
+ if (!mBaseUrl.matches(regexUrl)) {
+ System.out.println("Base URL: " + mBaseUrl);
+ throw new IllegalArgumentException(mContext.getString(R.string.error_illegal_url_argument));
+ }
+ } else {
+ if (!mUrl.matches(regexUrl)) {
+ System.out.println("URL: " + mUrl);
+ throw new IllegalArgumentException(mContext.getString(R.string.error_illegal_relative_url_argument_without_base));
+ }
+ }
+ }
+ }
+
+ @NonNull
+ private UpdateCheckerApi getCheckerApi() {
+ if (checkerApi == null) {
+ checkerApi = new Retrofit.Builder()
+ .baseUrl(mBaseUrl)
+ .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()))
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .build()
+ .create(UpdateCheckerApi.class);
+ }
+ return checkerApi;
+ }
+
+}
diff --git a/app/src/main/java/org/autojs/autojs/network/VersionService.java b/app/src/main/java/org/autojs/autojs/network/VersionService.java
deleted file mode 100644
index 695b9186..00000000
--- a/app/src/main/java/org/autojs/autojs/network/VersionService.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.autojs.autojs.network;
-
-import androidx.annotation.NonNull;
-
-import com.google.gson.GsonBuilder;
-import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
-
-import org.autojs.autojs.network.api.UpdateCheckApi;
-import org.autojs.autojs.network.entity.VersionInfo;
-
-import io.reactivex.Observable;
-import io.reactivex.schedulers.Schedulers;
-import retrofit2.Retrofit;
-import retrofit2.converter.gson.GsonConverterFactory;
-
-/**
- * Created by Stardust on 2017/9/20.
- * Modified by SuperMonster003 as of Feb 26, 2022.
- */
-public class VersionService {
-
- private static final String baseUrl = "https://raw.githubusercontent.com/";
-
- private static Retrofit mRetrofit;
-
- public static Observable checkForUpdates() {
- Retrofit mRetrofit = getRetrofit();
- return mRetrofit.create(UpdateCheckApi.class)
- .checkForUpdates()
- .subscribeOn(Schedulers.io());
- }
-
- @NonNull
- private static Retrofit getRetrofit() {
- if (mRetrofit == null) {
- mRetrofit = new Retrofit.Builder()
- .baseUrl(baseUrl)
- .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()))
- .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
- .build();
- }
- return mRetrofit;
- }
-
-}
diff --git a/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckApi.java b/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java
similarity index 52%
rename from app/src/main/java/org/autojs/autojs/network/api/UpdateCheckApi.java
rename to app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java
index b4cdc760..781d68fd 100644
--- a/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckApi.java
+++ b/app/src/main/java/org/autojs/autojs/network/api/UpdateCheckerApi.java
@@ -1,19 +1,23 @@
package org.autojs.autojs.network.api;
-import org.autojs.autojs.network.entity.VersionInfo;
+import java.io.InputStream;
import io.reactivex.Observable;
+import okhttp3.ResponseBody;
import retrofit2.http.GET;
import retrofit2.http.Headers;
+import retrofit2.http.Streaming;
+import retrofit2.http.Url;
/**
* Created by Stardust on 2017/9/20.
* Modified by SuperMonster003 as of Feb 28, 2022.
*/
-public interface UpdateCheckApi {
+public interface UpdateCheckerApi {
- @GET("/SuperMonster003/AutoJs6/master/project-versions.json")
+ @Streaming
+ @GET()
@Headers("Cache-Control: no-cache")
- Observable checkForUpdates();
+ Observable checkForUpdates(@Url String url);
}
diff --git a/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java b/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java
index 967d1ae6..05ca7062 100644
--- a/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java
+++ b/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java
@@ -1,19 +1,25 @@
package org.autojs.autojs.network.download;
-import com.stardust.app.GlobalAppContext;
-
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.util.Log;
+import android.widget.ProgressBar;
+import androidx.annotation.Nullable;
+
+import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
-import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
+import com.afollestad.materialdialogs.internal.MDButton;
+import com.stardust.app.GlobalAppContext;
import com.stardust.concurrent.VolatileBox;
import com.stardust.pio.PFiles;
-import org.autojs.autojs6.R;
import org.autojs.autojs.network.api.DownloadApi;
+import org.autojs.autojs.network.entity.VersionInfo;
import org.autojs.autojs.tool.SimpleObserver;
+import org.autojs.autojs.tool.UpdateUtils;
+import org.autojs.autojs6.R;
import java.io.File;
import java.io.FileOutputStream;
@@ -21,17 +27,21 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
+import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
+import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
/**
* Created by Stardust on 2017/10/20.
@@ -40,32 +50,38 @@ public class DownloadManager {
private static final String LOG_TAG = "DownloadManager";
private static DownloadManager sInstance;
-
private static final int RETRY_COUNT = 3;
+
private final DownloadApi mDownloadApi;
private final ConcurrentHashMap> mDownloadStatuses = new ConcurrentHashMap<>();
+ private Disposable mDisposable;
+
public DownloadManager() {
Retrofit mRetrofit = new Retrofit.Builder()
- .baseUrl("https://www.autojs.org")
+ .baseUrl(UpdateUtils.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
- .client(new OkHttpClient.Builder()
- .addInterceptor(chain -> {
- Request request = chain.request();
- Response response = chain.proceed(request);
- int tryCount = 0;
- while (!response.isSuccessful() && tryCount < RETRY_COUNT) {
- tryCount++;
- response = chain.proceed(request);
- }
- return response;
- })
- .build()
- )
+ .client(getOkHttpClient())
.build();
+
mDownloadApi = mRetrofit.create(DownloadApi.class);
}
+ private OkHttpClient getOkHttpClient() {
+ Interceptor interceptor = chain -> {
+ Request request = chain.request();
+ Response response = chain.proceed(request);
+ int tryCount = 0;
+ while (!response.isSuccessful() && tryCount < RETRY_COUNT) {
+ tryCount++;
+ response = chain.proceed(request);
+ }
+ return response;
+ };
+ return new OkHttpClient.Builder()
+ .addInterceptor(interceptor)
+ .build();
+ }
public static DownloadManager getInstance() {
if (sInstance == null) {
@@ -74,6 +90,11 @@ public class DownloadManager {
return sInstance;
}
+ public void disposeIfNeeded() {
+ if (mDisposable != null) {
+ mDisposable.dispose();
+ }
+ }
public static String parseFileNameLocally(String url) {
int i = url.lastIndexOf('-');
@@ -89,7 +110,7 @@ public class DownloadManager {
@SuppressWarnings("ResultOfMethodCallIgnored")
@SuppressLint("CheckResult")
- public Observable download(String url, String path) {
+ public Observable download(String url, String path) {
DownloadTask task = new DownloadTask(url, path);
mDownloadApi.download(url)
.subscribeOn(Schedulers.io())
@@ -98,26 +119,73 @@ public class DownloadManager {
}
public Observable downloadWithProgress(Context context, String url, String path) {
- String fileName = DownloadManager.parseFileNameLocally(url);
- return download(url, path, createDownloadProgressDialog(context, url, fileName));
+ String content = context.getString(R.string.text_file_name) + ": " + DownloadManager.parseFileNameLocally(url);
+ return downloadWithProgress(context, url, path, content);
}
- private MaterialDialog createDownloadProgressDialog(Context context, String url, String fileName) {
- return new MaterialDialog.Builder(context)
- .progress(false, 100)
- .title(fileName)
+ public Observable downloadWithProgress(Context context, String url, String path, VersionInfo versionInfo) {
+ return download(context, url, path, createDownloadProgressDialog(context, url, versionInfo, null));
+ }
+
+ public Observable downloadWithProgress(Context context, String url, String path, String content) {
+ return download(context, url, path, createDownloadProgressDialog(context, url, null, content));
+ }
+
+ private MaterialDialog createDownloadProgressDialog(Context context, String url, @Nullable VersionInfo versionInfo, @Nullable String content) {
+ MaterialDialog d = new MaterialDialog.Builder(context)
+ .title(context.getString(R.string.text_downloading))
+ .positiveText(R.string.dialog_button_cancel_download)
+ .onPositive((dialog, which) -> {
+ dialog.dismiss();
+ DownloadManager.getInstance().cancelDownload(url);
+ })
+ .progress(false, 100, true)
.cancelable(false)
- .positiveText(R.string.text_cancel_download)
- .onPositive((dialog, which) -> DownloadManager.getInstance().cancelDownload(url))
+ .autoDismiss(false)
.show();
+
+ String contentText = versionInfo != null ? versionInfo.toString() : content;
+ if (contentText != null) {
+ d.setContent(contentText);
+ }
+
+ d.setProgressNumberFormat(context.getString(R.string.text_half_ellipsis));
+
+ ProgressBar progressBar = d.getProgressBar();
+ progressBar.setProgressTintList(ColorStateList.valueOf(context.getColor(R.color.dialog_progress_download_tint)));
+ progressBar.setProgressBackgroundTintList(ColorStateList.valueOf(context.getColor(R.color.dialog_progress_download_bg_tint)));
+
+ MDButton positiveButton = d.getActionButton(DialogAction.POSITIVE);
+ positiveButton.setTextColor(context.getColor(R.color.dialog_progress_download_act_btn));
+
+ return d;
}
- private Observable download(String url, String path, MaterialDialog progressDialog) {
+ private Observable download(Context context, String url, String path, MaterialDialog progressDialog) {
PublishSubject subject = PublishSubject.create();
- DownloadManager.getInstance().download(url, path)
+ DownloadManager downloadMgr = DownloadManager.getInstance();
+ downloadMgr.download(url, path)
.observeOn(AndroidSchedulers.mainThread())
- .doOnNext(progressDialog::setProgress)
+ .doOnNext(o -> {
+ // 10,000 KB (around but less than 10 MB)
+ int megaThreshold = 10000 * (1 << 10);
+ if (o.getTotalBytes() > megaThreshold) {
+ progressDialog.setProgressNumberFormat(String.format(Locale.getDefault(),
+ context.getString(R.string.format_dialog_progress_number_format_mega_bytes),
+ o.getReadMegaBytes(), o.getTotalMegaBytes()));
+ } else {
+ progressDialog.setProgressNumberFormat(String.format(Locale.getDefault(),
+ context.getString(R.string.format_dialog_progress_number_format_kilo_bytes),
+ o.getReadKiloBytes(), o.getTotalKiloBytes()));
+ }
+ progressDialog.setProgress(o.getProgress());
+ })
.subscribe(new SimpleObserver<>() {
+ @Override
+ public void onSubscribe(Disposable disposable) {
+ mDisposable = disposable;
+ }
+
@Override
public void onComplete() {
progressDialog.dismiss();
@@ -129,6 +197,8 @@ public class DownloadManager {
public void onError(Throwable error) {
Log.e(LOG_TAG, "Download failed", error);
progressDialog.dismiss();
+ disposeIfNeeded();
+ getOkHttpClient().dispatcher().cancelAll();
subject.onError(error);
}
});
@@ -142,6 +212,11 @@ public class DownloadManager {
}
}
+ public boolean isCancelled(String url) {
+ VolatileBox status = mDownloadStatuses.get(url);
+ return status != null && !status.get();
+ }
+
private class DownloadTask {
private final String mUrl;
@@ -149,24 +224,27 @@ public class DownloadManager {
private final VolatileBox mStatus;
private InputStream mInputStream;
private FileOutputStream mFileOutputStream;
- private final PublishSubject mProgress;
+ private final PublishSubject mProgress;
public DownloadTask(String url, String path) {
mUrl = url;
mPath = path;
mStatus = new VolatileBox<>(true);
VolatileBox previous = mDownloadStatuses.put(mUrl, mStatus);
- if (previous != null)
+ if (previous != null) {
previous.set(false);
+ }
mProgress = PublishSubject.create();
}
- private void startImpl(ResponseBody body) throws IOException {
+ private void startImpl(ResponseBody body) throws Exception {
byte[] buffer = new byte[4096];
mFileOutputStream = new FileOutputStream(mPath);
mInputStream = body.byteStream();
long total = body.contentLength();
- long read = 0;
+
+ ProgressInfo o = new ProgressInfo(total);
+
while (true) {
if (!mStatus.get()) {
onCancel();
@@ -176,52 +254,51 @@ public class DownloadManager {
if (len == -1) {
break;
}
- read += len;
+ o.incrementRead(len);
mFileOutputStream.write(buffer, 0, len);
- if (total > 0) {
- mProgress.onNext((int) (100 * read / total));
+ if (o.getTotalBytes() > 0) {
+ mProgress.onNext(o);
}
}
mProgress.onComplete();
recycle();
}
- public void start(ResponseBody body) {
- try {
- PFiles.ensureDir(mPath);
- startImpl(body);
- } catch (Exception e) {
- mProgress.onError(e);
- }
+ public void start(ResponseBody body) throws Exception {
+ PFiles.ensureDir(mPath);
+ startImpl(body);
}
private void onCancel() {
+ GlobalAppContext.toast(R.string.text_download_cancelled);
recycle();
// TODO: 2017/12/6 notify?
}
public void recycle() {
+ // FIXME by SuperMonster003 on May 31, 2022.
+ // ! Seems like none of the ways below could stop the downloading process.
+ // ! Even worse, progress may stuck at around 99% and suspend.
+ // disposeIfNeeded();
+ // getOkHttpClient().dispatcher().cancelAll();
+ // body.close();
+
mDownloadStatuses.remove(mUrl);
- if (mInputStream != null) {
- try {
- mInputStream.close();
- } catch (IOException ignored) {
-
- }
+ try {
+ mInputStream.close();
+ } catch (IOException ignored) {
+ // Ignored.
}
- if (mFileOutputStream != null) {
- try {
- mFileOutputStream.close();
- } catch (IOException ignored) {
- }
+ try {
+ mFileOutputStream.close();
+ } catch (IOException ignored) {
+ // Ignored.
}
-
}
- public PublishSubject progress() {
+ public PublishSubject progress() {
return mProgress;
}
-
}
}
diff --git a/app/src/main/java/org/autojs/autojs/network/download/ProgressInfo.java b/app/src/main/java/org/autojs/autojs/network/download/ProgressInfo.java
new file mode 100644
index 00000000..ef3ae4f4
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/network/download/ProgressInfo.java
@@ -0,0 +1,58 @@
+package org.autojs.autojs.network.download;
+
+/**
+ * Created by SuperMonster003 on May 30, 2022.
+ */
+
+public class ProgressInfo {
+ private long mRead = 0;
+ private long mTotal;
+
+ public ProgressInfo(long contentLength) {
+ mTotal = contentLength;
+ }
+
+ public long getTotalBytes() {
+ return mTotal;
+ }
+
+ public float getTotalKiloBytes() {
+ return (float) (mTotal / Math.pow(2, 10));
+ }
+
+ public float getTotalMegaBytes() {
+ return (float) (mTotal / Math.pow(2, 20));
+ }
+
+ public void setTotal(long total) {
+ mTotal = total;
+ }
+
+ public void incrementTotal(long i) {
+ mTotal += i;
+ }
+
+ public long getReadBytes() {
+ return mRead;
+ }
+
+ public float getReadKiloBytes() {
+ return (float) (mRead / Math.pow(2, 10));
+ }
+
+ public float getReadMegaBytes() {
+ return (float) (mRead / Math.pow(2, 20));
+ }
+
+ public void setRead(long read) {
+ mRead = read;
+ }
+
+ public void incrementRead(long i) {
+ mRead += i;
+ }
+
+ public int getProgress() {
+ return (int) (mRead * 100 / mTotal);
+ }
+}
diff --git a/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.java b/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.java
index 02f11f21..864c2a87 100644
--- a/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.java
+++ b/app/src/main/java/org/autojs/autojs/network/entity/VersionInfo.java
@@ -2,27 +2,63 @@ package org.autojs.autojs.network.entity;
import androidx.annotation.NonNull;
+import com.stardust.app.GlobalAppContext;
+
import org.autojs.autojs6.BuildConfig;
+import org.autojs.autojs6.R;
+
+import java.text.MessageFormat;
/**
* Created by Stardust on 2017/9/20.
+ * Modified by SuperMonster003 as of May 29, 2022.
*/
public class VersionInfo {
- public int appVersionCode;
- public String appVersionName;
+ private String mVersionName;
+ private int mVersionCode;
+
+ public VersionInfo(@NonNull String propertiesFileRawString) {
+ String regexVersionName = "VERSION_NAME=.+";
+ String regexVersionCode = "VERSION_BUILD=.+";
+ for (String string : propertiesFileRawString.split("\n")) {
+ if (string.matches(regexVersionName)) {
+ mVersionName = string.split("=")[1];
+ } else if (string.matches(regexVersionCode)) {
+ mVersionCode = Integer.parseInt(string.split("=")[1]);
+ }
+ if (mVersionName != null && mVersionCode > 0) {
+ break;
+ }
+ }
+ }
+
+ public VersionInfo(@NonNull String versionName, int versionCode) {
+ mVersionName = versionName;
+ mVersionCode = versionCode;
+ }
public boolean isNewer() {
- return appVersionCode > BuildConfig.VERSION_CODE;
+ return mVersionCode > BuildConfig.VERSION_CODE;
+ }
+
+ public boolean isNotIgnored() {
+ // TODO by SuperMonster003 on May 30, 2022.
+ return true;
}
@NonNull
@Override
public String toString() {
- return "UpdateInfo{" +
- "appVersionCode=" + appVersionCode +
- ", appVersionName='" + appVersionName + '\'' +
- '}';
+ return MessageFormat.format("{0}: {1} ({2})", GlobalAppContext.getString(R.string.text_version), mVersionName, mVersionCode);
+ }
+
+ public String getVersionName() {
+ return mVersionName;
+ }
+
+ public int getVersionCode() {
+ return mVersionCode;
}
}
diff --git a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java
index 34a492d3..4fc12342 100644
--- a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java
+++ b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java
@@ -21,6 +21,7 @@ import com.stardust.autojs.runtime.api.Device;
import com.stardust.util.MapBuilder;
import org.autojs.autojs6.BuildConfig;
+import org.autojs.autojs6.R;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
@@ -167,7 +168,7 @@ abstract public class JsonSocket extends Socket {
} else if (value instanceof JsonElement) {
data.add(key, (JsonElement) value);
} else {
- throw new IllegalArgumentException("cannot put value " + value + " into json");
+ throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_put_value_into_json, value));
}
}
diff --git a/app/src/main/java/org/autojs/autojs/timing/TimedTask.java b/app/src/main/java/org/autojs/autojs/timing/TimedTask.java
index 192c035e..0afe490c 100644
--- a/app/src/main/java/org/autojs/autojs/timing/TimedTask.java
+++ b/app/src/main/java/org/autojs/autojs/timing/TimedTask.java
@@ -6,10 +6,12 @@ import android.content.Intent;
import androidx.annotation.NonNull;
+import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.execution.ExecutionConfig;
import org.autojs.autojs.external.ScriptIntents;
import org.autojs.autojs.storage.database.BaseModel;
+import org.autojs.autojs6.R;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.LocalDateTime;
@@ -100,7 +102,7 @@ public class TimedTask extends BaseModel {
dayOfWeek++;
nextTimeMillis += TimeUnit.DAYS.toMillis(1);
}
- throw new IllegalStateException("Should not happen! timeFlag = " + mTimeFlag + ", dayOfWeek = " + DateTime.now().getDayOfWeek());
+ return -1;
}
public static long getDayOfWeekTimeFlag(int dayOfWeek) {
@@ -127,7 +129,7 @@ public class TimedTask extends BaseModel {
return FLAG_FRIDAY;
}
- throw new IllegalArgumentException("dayOfWeek = " + dayOfWeek);
+ throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "dayOfWeek", dayOfWeek));
}
public long getMillis() {
diff --git a/app/src/main/java/org/autojs/autojs/tool/ConsoleTool.java b/app/src/main/java/org/autojs/autojs/tool/ConsoleTool.java
index 1cb761dc..5d33b2d7 100644
--- a/app/src/main/java/org/autojs/autojs/tool/ConsoleTool.java
+++ b/app/src/main/java/org/autojs/autojs/tool/ConsoleTool.java
@@ -8,10 +8,16 @@ import org.autojs.autojs.ui.log.LogActivity_;
public class ConsoleTool {
- public static void launch() {
- LogActivity_.intent(GlobalAppContext.get())
- .flags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .start();
+ public static boolean launch() {
+ try {
+ LogActivity_.intent(GlobalAppContext.get())
+ .flags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .start();
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
}
}
diff --git a/app/src/main/java/org/autojs/autojs/tool/UpdateUtils.java b/app/src/main/java/org/autojs/autojs/tool/UpdateUtils.java
new file mode 100644
index 00000000..dceb2602
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/tool/UpdateUtils.java
@@ -0,0 +1,59 @@
+package org.autojs.autojs.tool;
+
+import android.content.Context;
+import android.view.View;
+
+import com.stardust.autojs.annotation.ScriptInterface;
+
+import org.autojs.autojs.network.UpdateChecker;
+import org.autojs.autojs.network.UpdateChecker.PromptMode;
+
+import okhttp3.ResponseBody;
+
+/**
+ * Created by SuperMonster003 on May 29, 2022.
+ */
+
+public class UpdateUtils {
+
+ public static String BASE_URL = "https://raw.githubusercontent.com/";
+ public static String RELATIVE_URL = "/SuperMonster003/AutoJs6/master/version.properties";
+ public static String URL = BASE_URL + RELATIVE_URL.substring(1);
+
+ @ScriptInterface
+ public static UpdateChecker getDialogChecker(Context context, String url, SimpleObserver callback) {
+ return getBuilder(context, url, callback)
+ .setPromptMode(PromptMode.DIALOG)
+ .build();
+ }
+
+ public static UpdateChecker getDialogChecker(Context context) {
+ return getDialogChecker(context, null, null);
+ }
+
+ @ScriptInterface
+ public static UpdateChecker getSnackbarChecker(View view, String url, SimpleObserver callback) {
+ return getBuilder(view, url, callback)
+ .setPromptMode(PromptMode.SNACKBAR)
+ .build();
+ }
+
+ public static UpdateChecker getSnackbarChecker(View view) {
+ return getSnackbarChecker(view, null, null);
+ }
+
+ private static UpdateChecker.Builder getBuilder(Context context, String url, SimpleObserver callback) {
+ return new UpdateChecker.Builder(context)
+ .setBaseUrl(BASE_URL)
+ .setUrl(url != null ? url : RELATIVE_URL)
+ .setCallback(callback);
+ }
+
+ private static UpdateChecker.Builder getBuilder(View view, String url, SimpleObserver callback) {
+ return new UpdateChecker.Builder(view)
+ .setBaseUrl(BASE_URL)
+ .setUrl(url != null ? url : RELATIVE_URL)
+ .setCallback(callback);
+ }
+
+}
diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.java
index f2216c9c..98ecd0a5 100644
--- a/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.java
+++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.java
@@ -29,12 +29,12 @@ import com.stardust.pio.PFiles;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;
-import org.autojs.autojs6.R;
import org.autojs.autojs.storage.file.TmpScriptFiles;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
import org.autojs.autojs.tool.Observers;
import org.autojs.autojs.ui.BaseActivity;
import org.autojs.autojs.ui.main.MainActivity_;
+import org.autojs.autojs6.R;
import java.io.File;
import java.io.IOException;
@@ -158,11 +158,29 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega
Log.d(LOG_TAG, "onActionModeStarted: " + mode);
Menu menu = mode.getMenu();
MenuItem item = menu.getItem(menu.size() - 1);
- menu.add(item.getGroupId(), R.id.action_delete_line, 10000, R.string.text_delete_line);
- menu.add(item.getGroupId(), R.id.action_copy_line, 20000, R.string.text_copy_line);
+ addMenuItem(menu, item.getGroupId(), R.id.action_delete_line, 10000, R.string.text_delete_line, () -> mEditorMenu.deleteLine());
+ addMenuItem(menu, item.getGroupId(), R.id.action_copy_line, 20000, R.string.text_copy_line, () -> mEditorMenu.copyLine());
super.onActionModeStarted(mode);
}
+ private void addMenuItem(Menu menu, int groupId, int itemId, int order, int titleRes, Runnable runnable) {
+ try {
+ menu.add(groupId, itemId, order, titleRes).setOnMenuItemClickListener(item -> {
+ try {
+ runnable.run();
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ });
+ } catch (Exception e) {
+ // @Example android.content.res.Resources.NotFoundException
+ // ! on MIUI devices (maybe more)
+ e.printStackTrace();
+ }
+ }
+
@Override
public void onSupportActionModeStarted(@NonNull androidx.appcompat.view.ActionMode mode) {
Log.d(LOG_TAG, "onSupportActionModeStarted: mode = " + mode);
@@ -215,14 +233,17 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega
new ThemeColorMaterialDialogBuilder(this)
.title(R.string.text_prompt)
.content(R.string.edit_exit_without_save_warn)
- .positiveText(R.string.text_cancel)
- .negativeText(R.string.text_save_and_exit)
- .neutralText(R.string.text_exit_directly)
- .onNegative((dialog, which) -> {
+ .neutralText(R.string.text_back)
+ .neutralColor(getColor(R.color.dialog_button_default))
+ .negativeText(R.string.text_exit_directly)
+ .negativeColor(getColor(R.color.dialog_button_caution))
+ .positiveText(R.string.text_save_and_exit)
+ .positiveColor(getColor(R.color.dialog_button_hint))
+ .onNegative((dialog, which) -> finishAndRemoveFromRecents())
+ .onPositive((dialog, which) -> {
mEditorView.saveFile();
finishAndRemoveFromRecents();
})
- .onNeutral((dialog, which) -> finishAndRemoveFromRecents())
.show();
}
diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java
index 3e33d334..c04b871a 100644
--- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java
+++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java
@@ -16,6 +16,7 @@ import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.autojs.script.JavaScriptSource;
import com.stardust.pio.PFiles;
+import org.autojs.autojs.tool.ConsoleTool;
import org.autojs.autojs6.R;
import org.autojs.autojs.model.indices.AndroidClass;
import org.autojs.autojs.model.indices.ClassSearchingItem;
@@ -23,7 +24,6 @@ import org.autojs.autojs.ui.project.BuildActivity;
import org.autojs.autojs.ui.project.BuildActivity_;
import org.autojs.autojs.ui.common.NotAskAgainDialog;
import org.autojs.autojs.ui.edit.editor.CodeEditor;
-import org.autojs.autojs.ui.log.LogActivity_;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.ClipboardUtil;
@@ -54,12 +54,10 @@ public class EditorMenu {
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_log) {
- showLog();
- return true;
+ return ConsoleTool.launch();
}
if (itemId == R.id.action_force_stop) {
- forceStop();
- return true;
+ return tryDoing(mEditorView::forceStop);
}
return onEditOptionsSelected(item)
|| onJumpOptionsSelected(item)
@@ -79,8 +77,7 @@ public class EditorMenu {
.content(R.string.hint_long_click_run_to_debug)
.positiveText(R.string.text_ok)
.show();
- mEditorView.debug();
- return true;
+ return tryDoing(mEditorView::debug);
}
if (itemId == R.id.action_remove_all_breakpoints) {
mEditor.removeAllBreakpoints();
@@ -118,24 +115,20 @@ public class EditorMenu {
private boolean onMoreOptionsSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_console) {
- showConsole();
- return true;
+ return tryDoing(mEditorView::showConsole);
}
if (itemId == R.id.action_import_java_class) {
importJavaPackageOrClass();
return true;
}
if (itemId == R.id.action_editor_text_size) {
- mEditorView.selectTextSize();
- return true;
+ return tryDoing(mEditorView::selectTextSize);
}
if (itemId == R.id.action_editor_theme) {
- mEditorView.selectEditorTheme();
- return true;
+ return tryDoing(mEditorView::selectEditorTheme);
}
if (itemId == R.id.action_open_by_other_apps) {
- openByOtherApps();
- return true;
+ return tryDoing(mEditorView::openByOtherApps);
}
if (itemId == R.id.action_info) {
showInfo();
@@ -212,20 +205,19 @@ public class EditorMenu {
return true;
}
if (itemId == R.id.action_copy_line) {
- copyLine();
- return true;
+ return copyLine();
}
if (itemId == R.id.action_delete_line) {
- deleteLine();
- return true;
+ return deleteLine();
+ }
+ if (itemId == R.id.action_paste) {
+ return paste();
}
if (itemId == R.id.action_clear) {
- mEditor.setText("");
- return true;
+ return tryDoing(() -> mEditor.setText(""));
}
if (itemId == R.id.action_beautify) {
- beautifyCode();
- return true;
+ return tryDoing(mEditorView::beautifyCode);
}
return false;
}
@@ -255,11 +247,12 @@ public class EditorMenu {
}
private void showInfo() {
- Observable.zip(Observable.just(mEditor.getText()), mEditor.getLineCount(), (text, lineCount) -> {
- String size = PFiles.getHumanReadableSize(text.length());
- return String.format(Locale.getDefault(), mContext.getString(R.string.format_editor_info),
- text.length(), lineCount, size);
- })
+ Observable
+ .zip(Observable.just(mEditor.getText()), mEditor.getLineCount(), (text, lineCount) -> {
+ String size = PFiles.getHumanReadableSize(text.length());
+ return String.format(Locale.getDefault(), mContext.getString(R.string.format_editor_info),
+ text.length(), lineCount, size);
+ })
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::showInfo);
@@ -272,20 +265,21 @@ public class EditorMenu {
.show();
}
- private void copyLine() {
- mEditor.copyLine();
+ protected boolean copyLine() {
+ return tryDoing(mEditor::copyLine);
}
-
- private void deleteLine() {
- mEditor.deleteLine();
+ protected boolean deleteLine() {
+ return tryDoing(mEditor::deleteLine);
}
- private void paste() {
- CharSequence clip = getClip();
- if (clip != null) {
- mEditor.insert(clip.toString());
- }
+ protected boolean paste() {
+ return tryDoing(() -> {
+ CharSequence clip = getClip();
+ if (clip != null) {
+ mEditor.insert(clip.toString());
+ }
+ });
}
@Nullable
@@ -309,25 +303,14 @@ public class EditorMenu {
Snackbar.make(mEditorView, R.string.text_already_copied_to_clip, Snackbar.LENGTH_SHORT).show();
}
-
- private void showLog() {
- LogActivity_.intent(mContext).start();
- }
-
- private void showConsole() {
- mEditorView.showConsole();
- }
-
- private void forceStop() {
- mEditorView.forceStop();
- }
-
- private void openByOtherApps() {
- mEditorView.openByOtherApps();
- }
-
- private void beautifyCode() {
- mEditorView.beautifyCode();
+ private boolean tryDoing(Runnable callable) {
+ try {
+ callable.run();
+ return true;
+ } catch (Exception ignore) {
+ // Ignored.
+ }
+ return false;
}
}
diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/HVScrollView.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/HVScrollView.java
index 8510a2e9..4d21ee41 100644
--- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/HVScrollView.java
+++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/HVScrollView.java
@@ -15,6 +15,8 @@ import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.Scroller;
+import org.autojs.autojs6.R;
+
import java.util.List;
@@ -194,40 +196,34 @@ public class HVScrollView extends FrameLayout {
@Override
public void addView(View child) {
- if (getChildCount() > 0) {
- throw new IllegalStateException("ScrollView can host only one direct child");
- }
-
+ ensureNotMoreThanOneChild();
super.addView(child);
}
@Override
public void addView(View child, int index) {
- if (getChildCount() > 0) {
- throw new IllegalStateException("ScrollView can host only one direct child");
- }
-
+ ensureNotMoreThanOneChild();
super.addView(child, index);
}
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
- if (getChildCount() > 0) {
- throw new IllegalStateException("ScrollView can host only one direct child");
- }
-
+ ensureNotMoreThanOneChild();
super.addView(child, params);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
- if (getChildCount() > 0) {
- throw new IllegalStateException("ScrollView can host only one direct child");
- }
-
+ ensureNotMoreThanOneChild();
super.addView(child, index, params);
}
+ private void ensureNotMoreThanOneChild() {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException(getContext().getString(R.string.error_only_one_child_for_scroll_view));
+ }
+ }
+
/**
* @return Returns true this ScrollView can be scrolled
*/
diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/JavaScriptHighlighter.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/JavaScriptHighlighter.java
index 60cf739e..ad6d63d2 100644
--- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/JavaScriptHighlighter.java
+++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/JavaScriptHighlighter.java
@@ -23,7 +23,6 @@ import java.util.concurrent.atomic.AtomicInteger;
public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChangedListener {
-
public static class HighlightTokens {
public final int[] colors;
@@ -90,7 +89,6 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
updateTokens(s.toString());
}
-
public void setTheme(Theme theme) {
mTheme = theme;
}
@@ -112,7 +110,6 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
} catch (IOException neverHappen) {
throw new UncheckedIOException(neverHappen);
}
-
});
}
diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java
index 5a595d0a..e75edf87 100644
--- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java
+++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java
@@ -2,6 +2,8 @@ package org.autojs.autojs.ui.edit.toolbar;
import android.os.Bundle;
import android.os.Handler;
+
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import android.text.TextUtils;
@@ -69,7 +71,7 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mEditorView = findEditorView(view);
mDebugger = DebuggerSingleton.get();
diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java
index c5cbfd9c..2cee6b16 100644
--- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java
+++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java
@@ -1,6 +1,8 @@
package org.autojs.autojs.ui.edit.toolbar;
import android.os.Bundle;
+
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
@@ -17,9 +19,12 @@ public class SearchToolbarFragment extends ToolbarFragment {
public static final String ARGUMENT_SHOW_REPLACE_ITEM = "show_replace_item";
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- boolean showReplaceItem = getArguments().getBoolean(ARGUMENT_SHOW_REPLACE_ITEM, false);
+ boolean showReplaceItem = false;
+ if (getArguments() != null) {
+ showReplaceItem = getArguments().getBoolean(ARGUMENT_SHOW_REPLACE_ITEM, false);
+ }
view.findViewById(R.id.replace).setVisibility(showReplaceItem ? View.VISIBLE : View.GONE);
}
diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java
index b0d85575..e222b0e0 100644
--- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java
+++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java
@@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment;
import android.view.View;
import org.autojs.autojs.ui.edit.EditorView;
+import org.autojs.autojs6.R;
import java.util.List;
@@ -48,7 +49,7 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi
view = (View) view.getParent();
}
if (!(view instanceof EditorView)) {
- throw new IllegalStateException("cannot find EditorView from child: " + view);
+ throw new IllegalStateException(getString(R.string.error_cannot_find_editor_view_from_child_view, view));
}
return (EditorView) view;
}
diff --git a/app/src/main/java/org/autojs/autojs/ui/error/AbstractIssueReporterActivity.java b/app/src/main/java/org/autojs/autojs/ui/error/AbstractIssueReporterActivity.java
index e3336326..3f1389c1 100644
--- a/app/src/main/java/org/autojs/autojs/ui/error/AbstractIssueReporterActivity.java
+++ b/app/src/main/java/org/autojs/autojs/ui/error/AbstractIssueReporterActivity.java
@@ -38,6 +38,7 @@ import com.heinrichreimersoftware.androidissuereporter.model.github.GithubLogin;
import com.heinrichreimersoftware.androidissuereporter.model.github.GithubTarget;
import com.heinrichreimersoftware.androidissuereporter.util.ColorUtils;
import com.heinrichreimersoftware.androidissuereporter.util.ThemeUtils;
+import com.stardust.app.GlobalAppContext;
import com.stardust.theme.ThemeColorManager;
import org.autojs.autojs6.BuildConfig;
@@ -235,7 +236,8 @@ public abstract class AbstractIssueReporterActivity extends BaseActivity {
sendBugReport(new GithubLogin(username, password), null);
} else {
if (TextUtils.isEmpty(token))
- throw new IllegalStateException("You must provide a GitHub API Token.");
+ throw new IllegalStateException(GlobalAppContext
+ .getString(org.autojs.autojs6.R.string.error_github_api_token_needed));
String email = null;
if (!TextUtils.isEmpty(inputEmail.getText()) &&
diff --git a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.java b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.java
index 68c14487..76fef950 100644
--- a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.java
+++ b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.java
@@ -48,9 +48,12 @@ import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;
+import org.autojs.autojs.Pref;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.external.foreground.MainActivityForegroundService;
import org.autojs.autojs.model.explorer.Explorers;
+import org.autojs.autojs.network.UpdateChecker;
+import org.autojs.autojs.tool.UpdateUtils;
import org.autojs.autojs.ui.BaseActivity;
import org.autojs.autojs.ui.common.NotAskAgainDialog;
import org.autojs.autojs.ui.doc.DocsFragment_;
@@ -101,6 +104,7 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
@Override
protected void onPostResume() {
restartIfNeeded();
+ autoCheckForUpdatesIfNeeded();
super.onPostResume();
}
@@ -111,6 +115,19 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
}
}
+ private void autoCheckForUpdatesIfNeeded() {
+ if (Pref.isAutoCheckForUpdatesEnabled()) {
+ long minCheckedInterval = 2 * 60 * 60 * 1000; // 2 hours
+ long lastChecked = Pref.getLastUpdatesCheckedTimestamp();
+
+ if (System.currentTimeMillis() - lastChecked > minCheckedInterval) {
+ View rootView = findViewById(android.R.id.content);
+ UpdateChecker checker = UpdateUtils.getSnackbarChecker(rootView);
+ checker.checkNow();
+ }
+ }
+ }
+
public void applyThemeColor() {
if (ThemeColor.fromPreferences(PreferenceManager.getDefaultSharedPreferences(this), null) == null) {
ThemeColor defaultThemeColor = new ThemeColor(
diff --git a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java
index 0cf72453..4e977b55 100644
--- a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java
+++ b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java
@@ -189,7 +189,10 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
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)
+ new DrawerMenuItem(R.drawable.ic_personalize, R.string.text_theme_color, this::launchThemeColorSettings),
+
+ new DrawerMenuGroup(R.string.text_about),
+ new DrawerMenuItem(R.drawable.ic_about, R.string.text_about_app_and_developer, this::launchAboutAppAndDeveloper)
)));
mDrawerMenu.setAdapter(mDrawerMenuAdapter);
mDrawerMenu.setLayoutManager(new LinearLayoutManager(mContext));
@@ -266,7 +269,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
syncForegroundServiceState();
})
.onPositive((dialog, which) -> MainActivityForegroundService.start(mContext))
- .canceledOnTouchOutside(false)
.cancelable(false)
.show();
}
@@ -307,7 +309,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
syncUsageStatsPermissionState();
})
.onPositive((dialog, which) -> requestAppUsagePermission())
- .canceledOnTouchOutside(false)
.cancelable(false)
.show();
}
@@ -350,10 +351,14 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
return ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE)).isIgnoringBatteryOptimizations(mPackageName);
}
- public void openThemeColorSettings(DrawerMenuItemViewHolder holder) {
+ public void launchThemeColorSettings(DrawerMenuItemViewHolder holder) {
SettingsActivity.selectThemeColor(getActivity());
}
+ public void launchAboutAppAndDeveloper(DrawerMenuItemViewHolder holder) {
+ SettingsActivity.launchAboutAppAndDeveloper(requireActivity());
+ }
+
public void toggleNightMode(@NonNull DrawerMenuItemViewHolder holder) {
((BaseActivity) requireActivity()).setNightModeEnabled(holder.getSwitchCompat().isChecked());
}
@@ -402,7 +407,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
syncWriteSecuritySettingsState();
})
.onPositive((dialog, which) -> grantWriteSecureSettingsAccess())
- .canceledOnTouchOutside(false)
.cancelable(false)
.show();
}
@@ -430,7 +434,6 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
syncProjectMediaAccessState();
})
.onPositive((dialog, which) -> grantProjectMediaAccess())
- .canceledOnTouchOutside(false)
.cancelable(false)
.show();
}
diff --git a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java
index e2b5143d..a691b5c2 100644
--- a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java
+++ b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java
@@ -3,6 +3,7 @@ package org.autojs.autojs.ui.main.task;
import android.content.Context;
import com.bignerdranch.expandablerecyclerview.model.Parent;
+import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.execution.ScriptExecution;
import org.autojs.autojs6.R;
@@ -69,7 +70,7 @@ public abstract class TaskGroup implements Parent {
} else if (task instanceof IntentTask) {
mTasks.add(new Task.PendingTask((IntentTask) task));
} else {
- throw new IllegalArgumentException("task = " + task);
+ throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "task", task));
}
return pos;
}
@@ -100,7 +101,7 @@ public abstract class TaskGroup implements Parent {
} else if (task instanceof IntentTask) {
((Task.PendingTask) mTasks.get(i)).setIntentTask((IntentTask) task);
} else {
- throw new IllegalArgumentException("task = " + task);
+ throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "task", task));
}
}
return i;
diff --git a/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java b/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java
index 2e1880b8..688bb4b5 100644
--- a/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java
+++ b/app/src/main/java/org/autojs/autojs/ui/project/BuildActivity.java
@@ -126,7 +126,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
}
private void downloadPlugin() {
- IntentUtil.browse(this, "https://cdn.jsdelivr.net/gh/SuperMonster002/Hello-Sockpuppet@master/" +
+ IntentUtil.browse(this, "https://raw.githubusercontent.com/SuperMonster002/Hello-Sockpuppet/master/" +
"%5B" + "auto.js" + "%5D" +
"%5B" + "apk_builder_plugin_4.1.1_alpha2" + "%5D" +
"%5B" + "arm-v7a" + "%5D" +
diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.java b/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.java
index 302fad0c..44e3d694 100644
--- a/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.java
+++ b/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.java
@@ -11,11 +11,11 @@ import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;
+import org.autojs.autojs.tool.UpdateUtils;
import org.autojs.autojs6.BuildConfig;
import org.autojs.autojs6.R;
import org.autojs.autojs.ui.BaseActivity;
import org.autojs.autojs.ui.error.IssueReporterActivity;
-import org.autojs.autojs.ui.update.UpdateCheckDialog;
import de.psdev.licensesdialog.LicenseResolver;
import de.psdev.licensesdialog.LicensesDialog;
@@ -113,8 +113,8 @@ public class AboutActivity extends BaseActivity {
@SuppressLint("NonConstantResourceId")
@Click(R.id.about_functions_button_update)
- void checkForUpdated() {
- new UpdateCheckDialog(this).show();
+ void checkForUpdates() {
+ UpdateUtils.getDialogChecker(this).checkNow();
}
@SuppressLint("NonConstantResourceId")
diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.java b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.java
new file mode 100644
index 00000000..27eee3cc
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.java
@@ -0,0 +1,60 @@
+package org.autojs.autojs.ui.settings;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.Preference;
+import android.util.AttributeSet;
+
+import androidx.preference.PreferenceManager;
+
+import org.autojs.autojs.Pref;
+import org.autojs.autojs6.R;
+
+/**
+ * Created by SuperMonster003 on May 31, 2022.
+ */
+
+public class CheckForUpdatesPreference extends Preference implements SharedPreferences.OnSharedPreferenceChangeListener {
+ public CheckForUpdatesPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ public CheckForUpdatesPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ SharedPreferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
+ mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ public CheckForUpdatesPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public CheckForUpdatesPreference(Context context) {
+ super(context);
+ init();
+ }
+
+ @Override
+ protected void onAttachedToActivity() {
+ setSummaryIfNeeded();
+ super.onAttachedToActivity();
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ setSummaryIfNeeded();
+ }
+
+ private void setSummaryIfNeeded() {
+ String lastChecked = Pref.getLastUpdatesCheckedTimeString();
+ if (lastChecked != null) {
+ setSummary(getContext().getString(R.string.text_last_updates_checked_time, lastChecked));
+ }
+ }
+}
diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java b/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java
index d93c80b0..69c2cbf6 100644
--- a/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java
+++ b/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java
@@ -1,6 +1,7 @@
package org.autojs.autojs.ui.settings;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -23,10 +24,10 @@ import com.stardust.util.MapBuilder;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
import org.autojs.autojs.Pref;
+import org.autojs.autojs.tool.UpdateUtils;
import org.autojs.autojs6.R;
import org.autojs.autojs.ui.BaseActivity;
import org.autojs.autojs.ui.common.NotAskAgainDialog;
-import org.autojs.autojs.ui.update.UpdateCheckDialog;
import java.util.ArrayList;
import java.util.Collection;
@@ -66,70 +67,6 @@ public class SettingsActivity extends BaseActivity {
.add(new Pair<>(R.color.theme_color_default, R.string.theme_color_default))
.list();
- public static void selectThemeColor(Context context) {
- List colorItems = new ArrayList<>(COLOR_ITEMS.size());
- for (Pair item : COLOR_ITEMS) {
- colorItems.add(new ColorSelectActivity.ColorItem(context.getString(item.second), ContextCompat.getColor(context, item.first)));
- }
- ColorSelectActivity.startColorSelect(context, context.getString(R.string.mt_color_picker_title), colorItems);
- }
-
- @NonNull
- private static LinkedHashMap getAvailableLanguages(@NonNull Context context) {
- LinkedHashMap map = new LinkedHashMap<>();
- map.put(context.getString(R.string.text_app_language_follow_system),
- ((BaseActivity) context)::setLocaleFollowSystem);
- map.put(context.getString(R.string.text_app_language_simplified_chinese),
- () -> ((BaseActivity) context).updateLocale(Locale.SIMPLIFIED_CHINESE));
- map.put(context.getString(R.string.text_app_language_english),
- () -> ((BaseActivity) context).updateLocale(Locale.ENGLISH));
- return map;
- }
-
- public static void selectAppLanguage(Context context) {
- LinkedHashMap languagesMap = getAvailableLanguages(context);
- Set languagesKey = languagesMap.keySet();
- Collection languagesRunnable = languagesMap.values();
-
- new MaterialDialog.Builder(context)
- .title(R.string.text_app_language)
- .items(languagesKey)
- .itemsCallbackSingleChoice(Pref.getAppLanguageIndex(), (dialog, itemView, position, text) -> true)
- .positiveText(R.string.text_ok)
- .onPositive((dialog, which) -> {
- int index = dialog.getSelectedIndex();
- Runnable run = (Runnable) languagesRunnable.toArray()[index];
- if (run != null) {
- Pref.setAppLanguageIndex(index);
- GlobalAppContext.post(run);
- }
- dialog.dismiss();
- })
- .negativeText(R.string.text_cancel)
- .onNegative((dialog, which) -> dialog.dismiss())
- .neutralText(R.string.text_dialog_button_go_to_settings)
- .onNeutral((dialog, which) -> {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClassName("com.android.settings", "com.android.settings.LanguageSettings");
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
- })
- .autoDismiss(false)
- .show();
-
- showImperfectHint(context);
- }
-
- private static void showImperfectHint(Context context) {
- new NotAskAgainDialog.Builder(context, "SettingsActivity.select_app_language_imperfect_hint")
- .title(R.string.text_notice)
- .content(R.string.text_imperfect_hint_for_app_language)
- .positiveText(R.string.text_ok)
- .onPositive((dialog, which) -> dialog.dismiss())
- .autoDismiss(false)
- .show();
- }
-
@AfterViews
void setUpUI() {
setUpToolbar();
@@ -163,14 +100,28 @@ public class SettingsActivity extends BaseActivity {
@Override
public void onStart() {
super.onStart();
+ Activity mActivity = getActivity();
ACTION_MAP = new MapBuilder()
- .put(getString(R.string.text_theme_color), () -> selectThemeColor(getActivity()))
- .put(getString(R.string.text_check_for_updates), () -> new UpdateCheckDialog(getActivity()).show())
- .put(getString(R.string.text_about_app_and_developer), () -> startActivity(new Intent(getActivity(), AboutActivity_.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)))
- .put(getString(R.string.text_app_language), () -> selectAppLanguage(getActivity()))
+ .put(getString(R.string.text_theme_color), () -> selectThemeColor(mActivity))
+ .put(getString(R.string.text_about_app_and_developer), () -> launchAboutAppAndDeveloper(mActivity))
+ .put(getString(R.string.text_app_language), () -> selectAppLanguage(mActivity))
+ .put(getString(R.string.text_check_for_updates), () -> checkForUpdates(mActivity))
+ .put(getString(R.string.text_manage_ignored_updates), () -> manageIgnoredUpdates(mActivity))
.build();
}
+ private void manageIgnoredUpdates(Activity mActivity) {
+ // TODO by SuperMonster003 on May 31, 2022.
+ // ! Updates ignorance.
+
+ new MaterialDialog.Builder(mActivity)
+ .title(R.string.text_prompt)
+ .content(R.string.text_under_development_content)
+ .positiveText(R.string.dialog_button_back)
+ .build()
+ .show();
+ }
+
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
CharSequence title = preference.getTitle();
if (title != null) {
@@ -185,4 +136,76 @@ public class SettingsActivity extends BaseActivity {
}
+ public static void checkForUpdates(Context context) {
+ UpdateUtils.getDialogChecker(context).checkNow();
+ }
+
+ public static void selectThemeColor(Context context) {
+ List colorItems = new ArrayList<>(COLOR_ITEMS.size());
+ for (Pair item : COLOR_ITEMS) {
+ colorItems.add(new ColorSelectActivity.ColorItem(context.getString(item.second), ContextCompat.getColor(context, item.first)));
+ }
+ ColorSelectActivity.startColorSelect(context, context.getString(R.string.mt_color_picker_title), colorItems);
+ }
+
+ public static void selectAppLanguage(Context context) {
+ LinkedHashMap languagesMap = getAvailableLanguages(context);
+ Set languagesKey = languagesMap.keySet();
+ Collection languagesRunnable = languagesMap.values();
+
+ new MaterialDialog.Builder(context)
+ .title(R.string.text_app_language)
+ .items(languagesKey)
+ .itemsCallbackSingleChoice(Pref.getAppLanguageIndex(), (dialog, itemView, position, text) -> true)
+ .positiveText(R.string.text_ok)
+ .onPositive((dialog, which) -> {
+ int index = dialog.getSelectedIndex();
+ Runnable run = (Runnable) languagesRunnable.toArray()[index];
+ if (run != null) {
+ Pref.setAppLanguageIndex(index);
+ GlobalAppContext.post(run);
+ }
+ dialog.dismiss();
+ })
+ .negativeText(R.string.text_cancel)
+ .onNegative((dialog, which) -> dialog.dismiss())
+ .neutralText(R.string.dialog_button_go_to_settings)
+ .onNeutral((dialog, which) -> {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName("com.android.settings", "com.android.settings.LanguageSettings");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ })
+ .autoDismiss(false)
+ .show();
+
+ showImperfectHint(context);
+ }
+
+ public static void launchAboutAppAndDeveloper(@NonNull Context context) {
+ context.startActivity(new Intent(context, AboutActivity_.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ @NonNull
+ private static LinkedHashMap getAvailableLanguages(@NonNull Context context) {
+ LinkedHashMap map = new LinkedHashMap<>();
+ map.put(context.getString(R.string.text_app_language_follow_system),
+ ((BaseActivity) context)::setLocaleFollowSystem);
+ map.put(context.getString(R.string.text_app_language_simplified_chinese),
+ () -> ((BaseActivity) context).updateLocale(Locale.SIMPLIFIED_CHINESE));
+ map.put(context.getString(R.string.text_app_language_english),
+ () -> ((BaseActivity) context).updateLocale(Locale.ENGLISH));
+ return map;
+ }
+
+ private static void showImperfectHint(Context context) {
+ new NotAskAgainDialog.Builder(context, "SettingsActivity.select_app_language_imperfect_hint")
+ .title(R.string.text_notice)
+ .content(R.string.text_imperfect_hint_for_app_language)
+ .positiveText(R.string.text_ok)
+ .onPositive((dialog, which) -> dialog.dismiss())
+ .autoDismiss(false)
+ .show();
+ }
+
}
diff --git a/app/src/main/java/org/autojs/autojs/ui/update/UpdateCheckDialog.java b/app/src/main/java/org/autojs/autojs/ui/update/UpdateCheckDialog.java
deleted file mode 100644
index 1d3b3ec5..00000000
--- a/app/src/main/java/org/autojs/autojs/ui/update/UpdateCheckDialog.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.autojs.autojs.ui.update;
-
-import android.content.Context;
-
-import com.afollestad.materialdialogs.MaterialDialog;
-
-import org.autojs.autojs6.R;
-
-/**
- * Created by Stardust on 2017/9/20.
- */
-public class UpdateCheckDialog {
-
- private final MaterialDialog mProgress;
- private final Context mContext;
-
- public UpdateCheckDialog(Context context) {
- mContext = context;
- mProgress = new MaterialDialog.Builder(context)
- .title(R.string.text_under_development_title)
- .content(R.string.text_under_development_content)
- .positiveText(R.string.text_ok)
- .build();
-// mProgress = new MaterialDialog.Builder(context)
-// .progress(true, 0)
-// .content(R.string.text_checking_update)
-// .build();
- }
-
- public void show() {
- mProgress.show();
-// VersionService.checkForUpdates()
-// .observeOn(AndroidSchedulers.mainThread())
-// .subscribe(new SimpleObserver<>() {
-// @Override
-// public void onNext(@NonNull VersionInfo versionInfo) {
-// mProgress.dismiss();
-// if (versionInfo.isNewer()) {
-// new UpdateInfoDialogBuilder(mContext, versionInfo).show();
-// } else {
-// GlobalAppContext.toast(R.string.text_is_latest_version);
-// }
-// }
-//
-// @Override
-// public void onError(@NonNull Throwable e) {
-// e.printStackTrace();
-// mProgress.dismiss();
-// GlobalAppContext.toast(R.string.text_check_update_error);
-// }
-// });
- }
-}
diff --git a/app/src/main/java/org/autojs/autojs/ui/update/UpdateInfoDialogBuilder.java b/app/src/main/java/org/autojs/autojs/ui/update/UpdateInfoDialogBuilder.java
deleted file mode 100644
index c137cef4..00000000
--- a/app/src/main/java/org/autojs/autojs/ui/update/UpdateInfoDialogBuilder.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.autojs.autojs.ui.update;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.SharedPreferences;
-import androidx.preference.PreferenceManager;
-
-import androidx.annotation.NonNull;
-
-import com.afollestad.materialdialogs.MaterialDialog;
-import com.stardust.app.GlobalAppContext;
-import com.stardust.util.IntentUtil;
-
-import org.autojs.autojs.Pref;
-import org.autojs.autojs6.R;
-import org.autojs.autojs.external.fileprovider.AppFileProvider;
-import org.autojs.autojs.network.download.DownloadManager;
-import org.autojs.autojs.network.entity.VersionInfo;
-
-import java.io.File;
-
-import io.reactivex.android.schedulers.AndroidSchedulers;
-
-/**
- * Created by Stardust on 2017/4/9.
- */
-public class UpdateInfoDialogBuilder extends MaterialDialog.Builder {
-
- private static final String KEY_IGNORED_VERSION_PREFIX = "Ignored version: ";
- private final SharedPreferences mSharedPreferences;
- private VersionInfo mVersionInfo;
-
- public UpdateInfoDialogBuilder(@NonNull Context context, VersionInfo info) {
- super(context);
- mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
- updateInfo(info);
- }
-
- public void updateInfo(VersionInfo info) {
- mVersionInfo = info;
- title(GlobalAppContext.getString(R.string.text_new_version_found));
- content(info.appVersionName + "\n" + info.appVersionCode);
- }
-
- @Override
- public MaterialDialog show() {
- if (mSharedPreferences.getBoolean(KEY_IGNORED_VERSION_PREFIX + mVersionInfo.appVersionCode, false)) {
- return null;
- }
- return super.show();
- }
-
- @SuppressWarnings("ResultOfMethodCallIgnored")
- @SuppressLint("CheckResult")
- private void directlyDownload(String downloadUrl) {
- final String path = new File(Pref.getScriptDirPath(), "autojs6-latest.apk").getPath();
- DownloadManager.getInstance().downloadWithProgress(getContext(), downloadUrl, path)
- .subscribeOn(AndroidSchedulers.mainThread())
- .subscribe(file -> IntentUtil.installApkOrToast(getContext(), file.getPath(), AppFileProvider.AUTHORITY),
- error -> {
- error.printStackTrace();
- GlobalAppContext.toast(R.string.text_download_failed);
- });
-
- }
-
-}
diff --git a/app/src/main/java/org/autojs/autojs/ui/viewmodel/ExplorerItemList.java b/app/src/main/java/org/autojs/autojs/ui/viewmodel/ExplorerItemList.java
index 77d6d304..6ceff975 100644
--- a/app/src/main/java/org/autojs/autojs/ui/viewmodel/ExplorerItemList.java
+++ b/app/src/main/java/org/autojs/autojs/ui/viewmodel/ExplorerItemList.java
@@ -2,9 +2,12 @@ package org.autojs.autojs.ui.viewmodel;
import android.content.SharedPreferences;
+import com.stardust.app.GlobalAppContext;
+
import org.autojs.autojs.model.explorer.ExplorerItem;
import org.autojs.autojs.model.explorer.ExplorerPage;
import org.autojs.autojs.model.explorer.ExplorerSorter;
+import org.autojs.autojs6.R;
import java.util.ArrayList;
import java.util.Comparator;
@@ -118,7 +121,7 @@ public class ExplorerItemList {
case SORT_TYPE_TYPE:
return ExplorerSorter.TYPE;
}
- throw new IllegalArgumentException("unknown type " + sortType);
+ throw new IllegalArgumentException(GlobalAppContext.getString(R.string.error_illegal_argument, "sortType", sortType));
}
public int groupCount() {
diff --git a/app/src/main/res/drawable/ic_about.png b/app/src/main/res/drawable/ic_about.png
new file mode 100644
index 00000000..52e3c869
Binary files /dev/null and b/app/src/main/res/drawable/ic_about.png differ
diff --git a/app/src/main/res/layout/activity_error_report.xml b/app/src/main/res/layout/activity_error_report.xml
index dd5a6065..af74ea5b 100644
--- a/app/src/main/res/layout/activity_error_report.xml
+++ b/app/src/main/res/layout/activity_error_report.xml
@@ -30,5 +30,5 @@
android:scrollbars="vertical"
android:textColor="@android:color/secondary_text_light"
android:textSize="16sp"
- tools:text="错误"/>
+ tools:text="@string/text_error"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 2edac342..4faa970e 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -26,7 +26,7 @@
android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolBarStyle"
android:title="@string/app_name"
- app:layout_scrollFlags="scroll|enterAlways"
+ app:layout_scrollFlags="enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay" />