From b8d7b764e29793aef2a2f381e002350acaf08ef8 Mon Sep 17 00:00:00 2001 From: PM Date: Thu, 29 Oct 2026 12:50:58 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E5=99=A8=E5=8F=82=E6=95=B0=E7=B1=BB=E5=9E=8B=E4=B8=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=BF=A1=E6=81=AF=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - clearTimeout / clearInterval / clearImmediate 接受任意类型 id,非数字时安全返回 false - v6-api __globals__.js 同步参数类型声明 - GlobalAppContext 改用 getApplicationInfo 读取版本信息,避免 getPackageInfo 在部分 ROM 上抛异常 --- .../com/stardust/autojs/runtime/api/Timers.kt | 15 +++++++------ .../src/main/js/v6-api/src_raw/__globals__.js | 2 ++ .../java/com/stardust/app/GlobalAppContext.kt | 21 +++++++++++-------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.kt b/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.kt index 545e4b131..2abed1507 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.kt +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.kt @@ -56,16 +56,19 @@ class Timers(private val mRuntime: ScriptRuntime) { return timerForCurrentThread.setInterval(listener, delay, *args.drop(2).toTypedArray()) } - fun clearTimeout(id: Int): Boolean { - return timerForCurrentThread.clearTimeout(id) + fun clearTimeout(id: Any?): Boolean { + val intId = (id as? Number)?.toInt() ?: return false + return timerForCurrentThread.clearTimeout(intId) } - fun clearInterval(id: Int): Boolean { - return timerForCurrentThread.clearInterval(id) + fun clearInterval(id: Any?): Boolean { + val intId = (id as? Number)?.toInt() ?: return false + return timerForCurrentThread.clearInterval(intId) } - fun clearImmediate(id: Int): Boolean { - return timerForCurrentThread.clearImmediate(id) + fun clearImmediate(id: Any?): Boolean { + val intId = (id as? Number)?.toInt() ?: return false + return timerForCurrentThread.clearImmediate(intId) } fun recycle() { diff --git a/autojs/src/main/js/v6-api/src_raw/__globals__.js b/autojs/src/main/js/v6-api/src_raw/__globals__.js index 69424685f..e207516ee 100644 --- a/autojs/src/main/js/v6-api/src_raw/__globals__.js +++ b/autojs/src/main/js/v6-api/src_raw/__globals__.js @@ -4,6 +4,8 @@ module.exports = function (runtime, global) { runtime.toast(text); } + global.toastX = runtime.toastX; + global.toastLog = function (text) { runtime.toast(text); global.log(text); diff --git a/common/src/main/java/com/stardust/app/GlobalAppContext.kt b/common/src/main/java/com/stardust/app/GlobalAppContext.kt index a0fcee3f1..999056485 100644 --- a/common/src/main/java/com/stardust/app/GlobalAppContext.kt +++ b/common/src/main/java/com/stardust/app/GlobalAppContext.kt @@ -3,6 +3,7 @@ package com.stardust.app import android.annotation.SuppressLint import android.app.Application import android.content.Context +import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.util.Log import androidx.core.content.ContextCompat @@ -91,10 +92,11 @@ object GlobalAppContext { val appName: String by lazy { try { val packageManager = get().packageManager - val packageInfo = packageManager.getPackageInfo( - get().packageName, 0 + val appInfo = packageManager.getApplicationInfo( + get().packageName, + PackageManager.GET_META_DATA ) - packageManager.getApplicationLabel(packageInfo.applicationInfo).toString() + packageManager.getApplicationLabel(appInfo).toString() } catch (e: Exception) { Log.e(TAG, e.stackTraceToString()) "unknown app" @@ -106,15 +108,16 @@ object GlobalAppContext { @get:Synchronized val appIcon: Drawable? get() { - try { + return try { val packageManager = get().packageManager - val packageInfo = packageManager.getPackageInfo( - get().packageName, 0 + val appInfo = packageManager.getApplicationInfo( + get().packageName, + PackageManager.GET_META_DATA ) - return packageManager.getApplicationIcon(packageInfo.applicationInfo) + packageManager.getApplicationIcon(appInfo) } catch (e: Exception) { - e.printStackTrace() + Log.e(TAG, "Failed to get app icon", e) + null } - return null } } \ No newline at end of file From 2cd36c982fa5667d289ebea90dafa5e6aef720d0 Mon Sep 17 00:00:00 2001 From: PM Date: Fri, 13 Nov 2026 13:36:59 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(root):=20=E4=BB=85=E5=9C=A8=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=B7=B2=E8=8E=B7=20root=E3=80=81=E4=BD=86=20:script?= =?UTF-8?q?=20=E6=9C=AA=E6=8E=88=E6=9D=83=E6=97=B6=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E6=9D=83=E9=99=90=E4=B8=8D=E5=AE=8C=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 RootTool.isRootGranted(),以 root 身份执行 id 判断主进程是否已获得 root 授权,用于区分「完全没有 root」和「有 root 但 Magisk 按进程名拒绝 :script」两种失败;完全无 root 的设备不再弹出误导性提示。 app 侧 AccessibilityServiceTool / AccessibilityProxyServiceImpl 的失败分支 先判断 isRootGranted();autojs 模块 AccessibilityServiceTool 只记日志, 提示统一由主进程判断。 与 setup-v7 的 ff0b5097 等价。 --- .../tool/AccessibilityProxyServiceImpl.kt | 21 +++++++++++++++---- .../autojs/tool/AccessibilityServiceTool.java | 9 ++++++-- .../java/org/autojs/autojs/tool/RootTool.java | 16 ++++++++++++++ .../AccessibilityServiceTool.java | 7 +++---- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/autojs/autojs/tool/AccessibilityProxyServiceImpl.kt b/app/src/main/java/org/autojs/autojs/tool/AccessibilityProxyServiceImpl.kt index f90b8e8a8..c22e10343 100644 --- a/app/src/main/java/org/autojs/autojs/tool/AccessibilityProxyServiceImpl.kt +++ b/app/src/main/java/org/autojs/autojs/tool/AccessibilityProxyServiceImpl.kt @@ -6,9 +6,12 @@ import android.content.ServiceConnection import android.os.IBinder import android.os.RemoteException import android.util.Log +import com.stardust.app.GlobalAppContext import com.stardust.app.service.AbstractAutoService import com.stardust.autojs.core.accessibility.AccessibilityProxyService import com.stardust.autojs.core.accessibility.IAccessibilityProxyService +import org.autojs.autojs.tool.RootTool +import org.autojs.autoxjs.R class AccessibilityProxyServiceImpl : AbstractAutoService() { @@ -60,14 +63,24 @@ class AccessibilityProxyServiceImpl : AbstractAutoService() { } override fun ensureEnabled(): Boolean { - return try { - val result = proxyService?.ensureEnabled() ?: false - Log.d(TAG, "enable: $result") - result + val result = try { + proxyService?.ensureEnabled() ?: false } catch (e: RemoteException) { Log.e(TAG, "启用无障碍服务失败", e) false } + Log.d(TAG, "enable: $result") + if (!result) { + // 仅当“应用已获得 root、但 :script 进程未授权”时才提示 root 不完整; + // 完全没有 root 权限时不作任何提示。 + if (RootTool.isRootGranted()) { + GlobalAppContext.toast( + GlobalAppContext.getString(R.string.text_root_permission_incomplete) + + GlobalAppContext.appName + ) + } + } + return result } override fun disable(): Boolean { diff --git a/app/src/main/java/org/autojs/autojs/tool/AccessibilityServiceTool.java b/app/src/main/java/org/autojs/autojs/tool/AccessibilityServiceTool.java index 323c26311..d177e8ec3 100644 --- a/app/src/main/java/org/autojs/autojs/tool/AccessibilityServiceTool.java +++ b/app/src/main/java/org/autojs/autojs/tool/AccessibilityServiceTool.java @@ -12,6 +12,7 @@ import com.stardust.autojs.core.accessibility.AccessibilityService; import com.stardust.autojs.core.util.ProcessShell; import com.stardust.view.accessibility.AccessibilityServiceUtils; +import org.autojs.autojs.tool.RootTool; import java.util.Locale; import java.util.concurrent.CompletableFuture; @@ -67,8 +68,12 @@ public static boolean enableAccessibilityServiceByRoot(Class Date: Fri, 13 Nov 2026 14:26:42 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(apkbuilder):=20=E6=89=93=E5=8C=85?= =?UTF-8?q?=E6=97=B6=E5=89=A5=E7=A6=BB=E6=A8=A1=E6=9D=BF=20manifest=20?= =?UTF-8?q?=E7=9A=84=20android:testOnly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 模板 APK 若由 Android Studio 的 Run 生成会带 android:testOnly="true", 打包产物继承后系统拒绝普通安装,报 "Failed to install test-only apk"。 在 ManifestEditor.MutableNodeImpl.onAttr() 里丢弃该属性 (AxmlWriter.NodeImpl.onAttr 只做 attrs.add,跳过无副作用)。 与 setup-v7 的 38c88917 等价。验证:真实脏模板 manifest 改写后 aapt2 dump xmltree 的 testOnly 命中数 1 -> 0,包名/版本改写仍生效。 --- .../stardust/autojs/apkbuilder/ManifestEditor.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apkbuilder/src/main/java/com/stardust/autojs/apkbuilder/ManifestEditor.java b/apkbuilder/src/main/java/com/stardust/autojs/apkbuilder/ManifestEditor.java index 2889c49a7..2e8d7369a 100755 --- a/apkbuilder/src/main/java/com/stardust/autojs/apkbuilder/ManifestEditor.java +++ b/apkbuilder/src/main/java/com/stardust/autojs/apkbuilder/ManifestEditor.java @@ -98,6 +98,17 @@ private class MutableNodeImpl extends AxmlWriter.NodeImpl { @Override protected void onAttr(AxmlWriter.Attr a) { + // 模板 APK 若由 Android Studio 的 Run 生成,manifest 会带 + // android:testOnly="true"(IDE 注入 android.injected.testOnly)。 + // 打包产物会原样继承,导致系统拒绝普通安装并报 + // "Failed to install test-only apk"。分发用的脚本 APK 不应保留 + // 该标记,这里直接丢弃此属性;AxmlWriter.NodeImpl.onAttr 只做 + // attrs.add(a),跳过不会留下任何副作用。 + if (a.name != null && a.ns != null + && NS_ANDROID.equals(a.ns.data) + && "testOnly".equals(a.name.data)) { + return; + } ManifestEditor.this.onAttr(a); super.onAttr(a); } From c5420c9159777a57549aec373cb5dea869563692 Mon Sep 17 00:00:00 2001 From: PM Date: Sat, 14 Nov 2026 16:30:02 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(shizuku):=20=E5=85=B3=E9=97=AD=20AppOps?= =?UTF-8?q?=20=E6=9D=83=E9=99=90=E6=97=B6=E6=81=A2=E5=A4=8D=20default?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=86=99=E5=85=A5=20ROM=20=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E7=9A=84=20ask?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 从 setup-v7 的 7a168848 取回本 PR 范围内遗漏的修复,对应侧边栏 「媒体投影权限」与「显示在应用上层」两个开关: - 关闭时写 default(MODE_DEFAULT=3,回落系统默认策略「每次都询问」), 不再写 ask;部分 ROM 把 ask 扩展为数值 5,标准工具显示「未知(5)」, 且会留下显式覆盖,永远回不到默认 - 用 JsonParser 结构化解析 AbstractShell.Result 的 code / result, 不再靠 result.contains("\"code\":0") 判断 shell 是否成功 - 新增 isAppOpsAllowed(stdout):逐行解析 appops get,只有 mode 恰为 allow 才算开启,default/foreground/ignore/deny/ask(5) 均为关闭, 避免包名或输出里别处的 allow 造成误判 - root 路径改为检查退出码并 destroy 进程 上游 autojs 模块已有 gson 依赖,无需新增依赖。 --- .../autojs/core/shizuku/ShizukuClient.kt | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/autojs/src/main/java/com/stardust/autojs/core/shizuku/ShizukuClient.kt b/autojs/src/main/java/com/stardust/autojs/core/shizuku/ShizukuClient.kt index 08dc47b3f..f98fc123b 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/shizuku/ShizukuClient.kt +++ b/autojs/src/main/java/com/stardust/autojs/core/shizuku/ShizukuClient.kt @@ -7,6 +7,7 @@ import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import com.google.gson.JsonParser import com.stardust.app.GlobalAppContext import com.stardust.autojs.BuildConfig import com.stardust.autojs.core.console.ConsoleImpl @@ -155,21 +156,57 @@ class ShizukuClient private constructor() : Shizuku.OnRequestPermissionResultLis client } + private const val APP_OPS_ALLOW = "allow" + private const val APP_OPS_DEFAULT = "default" + + /** + * 解析 ShizukuUserService.runShellCommand 返回的 JSON(AbstractShell.Result): + * `{ "code": 0, "error": "...", "result": "" }`。 + */ + private fun parseShellResult(json: String): Pair = runCatching { + val obj = JsonParser.parseString(json).asJsonObject + val code = obj.get("code")?.takeIf { it.isJsonPrimitive }?.asInt ?: -1 + val stdout = obj.get("result")?.takeIf { it.isJsonPrimitive }?.asString.orEmpty() + code to stdout + }.getOrDefault(-1 to "") + + /** + * `appops get` 的 stdout 形如 `PROJECT_MEDIA: allow; time=...`。 + * 只有 mode 恰好是 allow 才算「静默允许」;default / foreground / ignore / + * deny 以及部分 ROM 扩展的 ask(5) 都应显示为关闭。不能用 + * contains("allow"),否则包名、JSON 字段或别处的 allow 会误判。 + */ + private fun isAppOpsAllowed(stdout: String): Boolean = + stdout.lineSequence().any { line -> + val mode = line.substringAfter(':', "").trim() + mode == APP_OPS_ALLOW || + mode.startsWith("$APP_OPS_ALLOW;") || + mode.startsWith("$APP_OPS_ALLOW ") + } + suspend fun setAppOpsPermission(op: String, allow: Boolean): Boolean { val packageName = GlobalAppContext.get().packageName if (instance.packageName == null) { instance.setupService(packageName).join() } - val mode = if (allow) "allow" else "ask" + // 关闭必须写 default:AOSP 的 MODE_DEFAULT(3) 是「不留显式覆盖, + // 回落系统默认策略」,对应设置里的「每次都询问」。 + // 原先写 ask,部分 ROM 把它扩展成数值 5,标准工具显示「未知(5)」, + // 而且它和 allow / foreground 一样会留下覆盖,永远回不到默认。 + val mode = if (allow) APP_OPS_ALLOW else APP_OPS_DEFAULT + val command = "appops set $packageName $op $mode" return runCatching { if (instance.available && instance.userPermission) { val service = instance.ensureShizukuService() - val result = service.runShellCommand(0, "appops set $packageName $op $mode") - result.contains("\"code\":0") + val (code, _) = parseShellResult(service.runShellCommand(0, command)) + if (code == 0) true else null } else null }.getOrNull() ?: runCatching { - Runtime.getRuntime().exec(arrayOf("su", "-c", "appops set $packageName $op $mode")).waitFor() == 0 + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) + val code = process.waitFor() + process.destroy() + code == 0 }.getOrDefault(false) } @@ -178,19 +215,20 @@ class ShizukuClient private constructor() : Shizuku.OnRequestPermissionResultLis if (instance.packageName == null) { instance.setupService(packageName).join() } + val command = "appops get $packageName $op" return runCatching { if (instance.available && instance.userPermission) { val service = instance.ensureShizukuService() - val result = service.runShellCommand(0, "appops get $packageName $op") - result.contains("allow") + val (code, stdout) = parseShellResult(service.runShellCommand(0, command)) + if (code == 0) isAppOpsAllowed(stdout) else null } else null }.getOrNull() ?: runCatching { - val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "appops get $packageName $op")) - val result = process.inputStream.bufferedReader().readText() - process.waitFor() + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) + val stdout = process.inputStream.bufferedReader().readText() + val code = process.waitFor() process.destroy() - result.contains("allow") + if (code == 0) isAppOpsAllowed(stdout) else false }.getOrDefault(false) } }