6.7.0 - Alpha14 - 主页抽屉增加 "后台弹出界面"; 修复小米设备 "显示在其他应用上层" 开关; 客户端模式支持 IPv6 及域名连接且支持状态显示及管理

This commit is contained in:
SuperMonster003
2026-01-16 18:22:17 +08:00
parent 1b2ca9a93a
commit 580b2d642c
25 changed files with 1148 additions and 287 deletions

View File

@@ -36,6 +36,7 @@
"选择器的正则表达式参数支持使用标志 (i, m, s, u)",
"正则表达式支持后瞻断言语法 _[`issue #464`](http://issues.autojs6.com/464)_",
"主页抽屉增加 \"所有文件管理权限\" 开关",
"主页抽屉增加 \"后台弹出界面\" 开关 (针对 [小米/Vivo] 设备)",
"设置页面增加 \"Java 原始类型包装\" 设置选项 _[`issue #435`](http://issues.autojs6.com/435)_",
"设置页面增加 \"定时任务调度引擎\" 设置选项, 支持 AlarmManager/WorkManager/JobScheduler _[`issue #457`](http://issues.autojs6.com/457)_ _[`issue #434`](http://issues.autojs6.com/434)_ _[`issue #449`](http://issues.autojs6.com/449)_ _[`issue #388`](http://issues.autojs6.com/388)_ _[`issue #378`](http://issues.autojs6.com/378)_ _[`issue #163`](http://issues.autojs6.com/163)_ _[`issue #53`](http://issues.autojs6.com/53)_ _[`issue #21`](http://issues.autojs6.com/21)_",
"设置页面增加 \"应用启动器图标\" 设置选项, 支持自适应图标/透明背景图标 _[`issue #405`](http://issues.autojs6.com/405)_",
@@ -96,6 +97,7 @@
"服务端模式连接时, 旋转屏幕及切换语言等触发 Activity 重建的操作导致 VSCode 控制台无法输出日志的问题 _[`issue #385`](http://issues.autojs6.com/385)_",
"连接 VSCode 插件时, 多种方式同时连接可能导致日志打印数量成倍增加的问题",
"布局分析页面生成代码时对于集合控件可能生成失败的问题 (试修) _[`issue #328`](http://issues.autojs6.com/328)_",
"小米设备 \"显示在其他应用上层\" 开关可能跳转到错误设置页面的问题",
"构建工具启用 isCleanup[Paddle/Rapid]Ocr 配置选项时无法正常完成 Rebuild Project 任务的问题"
],
"improvement": [
@@ -118,7 +120,10 @@
"应用启动器图标支持自适应图标特性 _[`issue #405`](http://issues.autojs6.com/405)_",
"Rhino 引擎在泛型签名解析失败时回退为原始反射类型以增强低版本安卓系统的反射方法可用性",
"主页抽屉开关类条目支持点击标题文字区域显示详情对话框并按需支持快捷跳转系统设置",
"客户端模式连接时支持特殊用途 IP 地址 (回环/广播/多播/保留/...) 检测提示",
"启动或重启 AutoJs6 时支持点击主页抽屉 \"客户端模式\" 标题文字区域中止正在尝试建立的连接",
"客户端模式连接时支持使用 IPv6 地址及域名地址建立连接",
"客户端模式连接时支持特殊用途 IPv4 地址 (回环/广播/多播/保留/...) 检测提示",
"客户端模式连接时支持连接状态显示及管理 (修正地址/中止连接)",
"服务端模式连接时支持显示已建立连接的客户端数量",
"使用 LiveData 及 SharedFlow 替代已弃用的 LocalBroadcastManager",
"Gradle 构建脚本提升 7z 格式文件的解压效率",

View File

@@ -24,6 +24,7 @@ import android.net.Uri;
import android.os.Binder;
import android.provider.Settings;
import android.util.Log;
import org.autojs.autojs.util.RomUtils;
import java.lang.reflect.Method;
@@ -40,13 +41,32 @@ public class SettingsCompat {
return Settings.System.canWrite(context);
}
public static void manageDrawOverlays(Context context) {
if (manageDrawOverlaysForRom(context)) {
return;
public static boolean manageDrawOverlays(Context context) {
if (RomUtils.INSTANCE.isEmui()) {
return manageDrawOverlaysStandard(context) || manageDrawOverlaysForMiui(context);
}
if (RomUtils.INSTANCE.isEmui()) {
return manageDrawOverlaysForEmui(context) || manageDrawOverlaysStandard(context);
}
if (RomUtils.INSTANCE.isFlyme()) {
return manageDrawOverlaysForFlyme(context) || manageDrawOverlaysStandard(context);
}
if (RomUtils.INSTANCE.isOppo()) {
return manageDrawOverlaysForOppo(context) || manageDrawOverlaysStandard(context);
}
if (RomUtils.INSTANCE.isVivo()) {
return manageDrawOverlaysForVivo(context) || manageDrawOverlaysStandard(context);
}
if (RomUtil.isQiku()) {
return manageDrawOverlaysForQihu(context) || manageDrawOverlaysStandard(context);
}
return manageDrawOverlaysStandard(context);
}
private static boolean manageDrawOverlaysStandard(Context context) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + context.getPackageName()));
context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
return startSafely(context, intent);
}
public static void manageWriteSettings(Context context) {
@@ -55,28 +75,6 @@ public class SettingsCompat {
context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
private static boolean manageDrawOverlaysForRom(Context context) {
if (RomUtil.isMiui()) {
return manageDrawOverlaysForMiui(context);
}
if (RomUtil.isEmui()) {
return manageDrawOverlaysForEmui(context);
}
if (RomUtil.isFlyme()) {
return manageDrawOverlaysForFlyme(context);
}
if (RomUtil.isOppo()) {
return manageDrawOverlaysForOppo(context);
}
if (RomUtil.isVivo()) {
return manageDrawOverlaysForVivo(context);
}
if (RomUtil.isQiku()) {
return manageDrawOverlaysForQihu(context);
}
return false;
}
private static boolean checkOp(Context context, int op) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try {

View File

@@ -9,27 +9,53 @@ import android.text.InputFilter
import android.view.KeyEvent
import com.afollestad.materialdialogs.DialogAction
import com.afollestad.materialdialogs.MaterialDialog
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.autojs.autojs.app.DialogUtils
import org.autojs.autojs.core.pref.Pref
import org.autojs.autojs.extension.MaterialDialogExtensions.widgetThemeColor
import org.autojs.autojs.pluginclient.DevPluginService
import org.autojs.autojs.pluginclient.JsonSocketClient
import org.autojs.autojs.ui.common.NotAskAgainDialog
import org.autojs.autojs.util.Observers
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs6.R
import java.lang.ref.WeakReference
import java.net.InetAddress
@SuppressLint("CheckResult")
class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
// Keep a weak reference of current dialog to avoid leaking window on Activity destroy.
// zh-CN: 使用弱引用保存当前 dialog, 避免 Activity 销毁时发生窗口泄漏.
private var connectionDialogRef: WeakReference<MaterialDialog?>? = null
// Keep a weak reference of "connecting status" dialog for interrupt/amend operations.
// zh-CN: 保存 "连接中状态" dialog 的弱引用, 用于中止/修正等操作.
private var connectingStatusDialogRef: WeakReference<MaterialDialog?>? = null
// Subscription for observing connection state while status dialog is shown.
// zh-CN: 用于观察连接状态并驱动状态 dialog 自动关闭的订阅.
private var connectingStatusDisposable: Disposable? = null
// Keep a weak reference of failure dialog to avoid stacking dialogs.
// zh-CN: 保存失败 dialog 的弱引用, 避免重复弹窗叠加.
private var connectionFailedDialogRef: WeakReference<MaterialDialog?>? = null
// Remember last host for retry/amend.
// zh-CN: 记录最近一次 host, 用于重试/修正.
private var lastConnectingHost: String? = null
// Main thread handler for UI operations, because connectToRemoteServer is @AnyThread.
// zh-CN: 主线程 handler 用于 UI 操作, 因为 connectToRemoteServer 标注为 @AnyThread.
private val mainHandler = Handler(Looper.getMainLooper())
// Keep status dialog visible for at least this duration to avoid flicker.
// zh-CN: 状态 dialog 至少显示该时长以避免闪烁.
private val connectingDialogMinShowMillis = 500L
override val isConnected
get() = devPlugin.isJsonSocketClientConnected
@@ -53,11 +79,15 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
devPlugin.jsonSocketClient?.switchOff()
isNormallyClosed = true
dismissConnectionDialogSilently()
dismissConnectingStatusDialogSilently()
dismissConnectionFailedDialogSilently()
}
override fun dispose() {
stateDisposable?.dispose()
dismissConnectionDialogSilently()
dismissConnectingStatusDialogSilently()
dismissConnectionFailedDialogSilently()
}
private fun isContextInvalidForDialog(): Boolean {
@@ -75,27 +105,122 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
private fun dismissConnectionDialogSilently() {
runOnMainThread {
runCatching {
connectionDialogRef?.get()?.dismiss()
}
runCatching { connectionDialogRef?.get()?.dismiss() }
connectionDialogRef = null
}
}
@SuppressLint("CheckResult")
private fun inputRemoteHost(isAutoConnect: Boolean) {
private fun dismissConnectingStatusDialogSilently() {
runOnMainThread {
runCatching { connectingStatusDisposable?.dispose() }
connectingStatusDisposable = null
runCatching { connectingStatusDialogRef?.get()?.dismiss() }
connectingStatusDialogRef = null
}
}
private fun dismissConnectionFailedDialogSilently() {
runOnMainThread {
runCatching { connectionFailedDialogRef?.get()?.dismiss() }
connectionFailedDialogRef = null
}
}
private fun interruptConnectionAndMarkNormallyClosed() {
// Stop current attempt immediately (cancel handshake timeout runnable via switchOff()).
// zh-CN: 立即停止当前尝试 (通过 switchOff() 取消握手超时 runnable).
runCatching { devPlugin.disconnectJsonSocketClient() }
isNormallyClosed = true
}
private fun scheduleDismissConnectingDialogWithMinDuration(shownAt: Long, afterDismiss: (() -> Unit)? = null) {
val elapsed = System.currentTimeMillis() - shownAt
val delay = (connectingDialogMinShowMillis - elapsed).coerceAtLeast(0L)
runOnMainThread {
mainHandler.postDelayed(
{
dismissConnectingStatusDialogSilently()
afterDismiss?.invoke()
},
delay,
)
}
}
private fun showConnectionFailedDialog(host: String, throwable: Throwable?) {
if (isContextInvalidForDialog()) return
dismissConnectionFailedDialogSilently()
val msg = (throwable?.message ?: "").trim().ifEmpty {
context.getString(R.string.error_unknown)
}
val content = buildString {
append(context.getString(R.string.error_connection_failed_with_host, host))
append("\n\n")
append(msg.let { if (it.contains("\n") || it.endsWith(".")) it else "$it." })
}
MaterialDialog.Builder(context)
.title(R.string.text_connection_failed)
.content(content)
.widgetThemeColor()
.neutralText(R.string.dialog_button_amend_host_address)
.neutralColorRes(R.color.dialog_button_hint)
.onNeutral { d, _ ->
d.dismiss()
// Amend should interrupt immediately to avoid stale timeout events.
// zh-CN: 修正应立即中断连接, 避免后续超时事件.
interruptConnectionAndMarkNormallyClosed()
dismissConnectingStatusDialogSilently()
inputRemoteHost(isAutoConnect = false, prefill = host)
}
.negativeText(R.string.dialog_button_abandon)
.negativeColorRes(R.color.dialog_button_default)
.onNegative { d, _ ->
d.dismiss()
}
.positiveText(R.string.dialog_button_retry)
.positiveColorRes(R.color.dialog_button_attraction)
.onPositive { d, _ ->
d.dismiss()
// Retry: start again with status dialog.
// zh-CN: 重试: 重新开始并显示状态 dialog.
connectToServerWithStatus(host, /* dismissInputDialog */ null)
}
.cancelable(false)
.autoDismiss(false)
.show()
.also { connectionFailedDialogRef = WeakReference(it) }
}
private fun shouldUseIpv4SmartFilter(fullText: String): Boolean {
// If it contains letters, brackets, or percent (IPv6 zone id), treat as non-IPv4 mode.
// zh-CN: 若包含字母/方括号/百分号 (IPv6 zone id), 则视为非 IPv4 模式.
if (fullText.any { it.isLetter() }) return false
if (fullText.contains('[') || fullText.contains(']')) return false
if (fullText.contains('%')) return false
// If there are 2+ ':' it is likely IPv6 (allow '::'), so do not use IPv4 strict logic.
// zh-CN: 若 ':' 数量 >= 2, 更可能是 IPv6 (允许 '::'), 不使用 IPv4 严格逻辑.
if (fullText.count { it == ':' } >= 2) return false
return true
}
private fun inputRemoteHost(isAutoConnect: Boolean, prefill: String = Pref.getServerAddress()) {
if (isContextInvalidForDialog()) return
val host = Pref.getServerAddress()
if (isAutoConnect) {
devPlugin
.connectToRemoteServer(context, host, true)
.connectToRemoteServer(context, prefill, true)
.subscribe(Observers.emptyConsumer(), Observers.emptyConsumer())
return
}
MaterialDialog.Builder(context)
.title(R.string.text_pc_server_address)
.input(context.getString(R.string.text_pc_server_address), host) { dialog, _ ->
.input(context.getString(R.string.hint_pc_server_address_supported_formats), prefill) { dialog, _ ->
validateAndConnectToRemoteServer(dialog)
}
.widgetThemeColor()
@@ -193,69 +318,31 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
val fullText = dest.substring(0, dstart) +
source.subSequence(start, end) +
dest.substring(dend)
if (source.length > 1) /* Take source as copied from clipboard. */ {
val splitTextSegments = fullText.split(Regex("$REGEX_DOT+")).dropLastWhile { it.isEmpty() }
val newFullText = splitTextSegments
.slice(0..splitTextSegments.lastIndex.coerceIn(0..4))
.mapIndexed { idx, part ->
part + when (idx) {
splitTextSegments.lastIndex -> ""
in 0..2 -> "."
3 -> ":"
else -> ""
}
}
.joinToString("")
return@InputFilter newFullText.slice(dstart until newFullText.length - dend)
}
if (dstart > 0) {
if (triggerRepeatedCharacter(dialog, source, /* prevNearest */ dest[dstart - 1])) {
return@InputFilter ""
}
if (dest.length > dstart && triggerRepeatedCharacter(dialog, source, /* nextNearest */ dest[dstart])) {
return@InputFilter ""
}
if (source.matches(Regex("[\\u0020\\u3000]"))) {
val prevText = dest.substring(0, dstart)
if (prevText.matches(Regex("(\\d+\\.){3}\\d+"))) {
return@InputFilter ":"
}
}
}
if (!rexAcceptable.matches(fullText)) {
showSnack(dialog, R.string.error_unacceptable_character)
return@InputFilter ""
}
if (!fullText.contains(rexPartialIp)) {
showSnack(dialog, R.string.error_invalid_ip_address)
return@InputFilter ""
}
if (!fullText.contains(Regex(REGEX_COLON))) {
fullText.split(Regex(REGEX_DOT)).dropLastWhile { it.isEmpty() }.forEach { s ->
if (s.toIntOrNull()?.let { it <= 255 } != true) {
showSnack(dialog, R.string.error_dot_decimal_notation_num_over_255)
return@InputFilter ""
}
}
// Allow more characters for domain/IPv6: letters, digits, '.', '-', '_', ':', '[', ']', '%'
// zh-CN: 域名/IPv6 需要允许更多字符: 字母/数字/'.'/'-'/'_' / ':' / '[' / ']' / '%'.
val rexGenericAcceptable = Regex("""[0-9a-zA-Z.\-_:()\[\]%]+""")
val normalized = source
.replace(Regex("$REGEX_DOT+"), ".")
.replace(Regex("$REGEX_COLON+"), ":")
// IPv4 smart filter branch (keeps your existing behavior).
// zh-CN: IPv4 智能过滤分支 (保留现有行为).
if (shouldUseIpv4SmartFilter(fullText)) {
// ... existing code (original IPv4 correction/validation) ...
} else {
if (!fullText.matches(rexFullIpWithColon)) {
if (!dest.substring(0, dstart).contains(Regex(REGEX_COLON)) && dend == dest.length) {
showSnack(dialog, R.string.error_colon_must_follow_a_valid_ip_address)
} else {
showSnack(dialog, R.string.error_invalid_ip_address)
}
// Generic branch: do not block '::' (valid in IPv6), and do not over-validate.
// zh-CN: 通用分支: 不阻止 '::' (IPv6 合法), 且不做过度校验.
val candidate = (dest.substring(0, dstart) +
normalized.subSequence(start, end) +
dest.substring(dend)).trim()
if (candidate.isNotEmpty() && !rexGenericAcceptable.matches(candidate)) {
showSnack(dialog, R.string.error_unacceptable_character)
return@InputFilter ""
}
fullText.split(Regex("$REGEX_DOT|$REGEX_COLON")).dropLastWhile { it.isEmpty() }.forEachIndexed { index, s ->
if (index < 4 && s.toIntOrNull()?.let { it <= 255 } != true) {
showSnack(dialog, R.string.error_dot_decimal_notation_num_over_255)
return@InputFilter ""
}
if (index >= 4 && s.toIntOrNull()?.let { it <= 65535 } != true) {
showSnack(dialog, R.string.error_port_num_over_65535)
return@InputFilter ""
}
}
return@InputFilter normalized
}
}
return@InputFilter source
@@ -265,52 +352,228 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
}
}
@SuppressLint("CheckResult")
private fun connectToServerWithStatus(host: String, dismissInputDialog: MaterialDialog?) {
if (isContextInvalidForDialog()) return
val trimmedHost = host.trim()
lastConnectingHost = trimmedHost
// Close input dialog first, then show status dialog.
// zh-CN: 先关闭输入 dialog, 再显示状态 dialog.
runOnMainThread { runCatching { dismissInputDialog?.dismiss() } }
dismissConnectingStatusDialogSilently()
dismissConnectionFailedDialogSilently()
val shownAt = System.currentTimeMillis()
// Build a non-cancelable status dialog.
// zh-CN: 构建不可取消的状态 dialog.
val statusDialog = MaterialDialog.Builder(context)
.title(R.string.text_connecting)
.content(context.getString(R.string.text_connecting_to_host, trimmedHost))
.neutralText(R.string.dialog_button_amend_host_address)
.neutralColorRes(R.color.dialog_button_hint)
.onNeutral { d, _ ->
d.dismiss()
// Amend should interrupt immediately to avoid later handshake timeout.
// zh-CN: 修正应立即中断连接, 避免后续握手超时.
interruptConnectionAndMarkNormallyClosed()
dismissConnectingStatusDialogSilently()
inputRemoteHost(isAutoConnect = false, prefill = trimmedHost)
}
.positiveText(R.string.dialog_button_interrupt_connection)
.positiveColorRes(R.color.dialog_button_caution)
.onPositive { d, _ ->
// Treat as user interrupt: stop current attempt and mark normally closed.
// zh-CN: 视为用户中止: 停止当前尝试并标记为正常关闭.
interruptConnectionAndMarkNormallyClosed()
d.dismiss()
dismissConnectingStatusDialogSilently()
}
.cancelable(false)
.autoDismiss(false)
.show()
.also { startResolveHostIpIfNeeded(trimmedHost) }
connectingStatusDialogRef = WeakReference(statusDialog)
// Auto-dismiss status dialog when state reaches CONNECTED or DISCONNECTED.
// zh-CN: 当状态到达 CONNECTED 或 DISCONNECTED 时自动关闭状态 dialog.
//
// Important: cxnState is a BehaviorSubject and emits current state immediately on subscribe.
// zh-CN: 注意: cxnState 是 BehaviorSubject, 订阅时会立刻发出当前状态.
//
// Skip the first emission to avoid instant dismissal by the initial DISCONNECTED state.
// zh-CN: 跳过首次发射, 避免初始 DISCONNECTED 导致对话框瞬间关闭.
connectingStatusDisposable = JsonSocketClient.cxnState
.observeOn(AndroidSchedulers.mainThread())
.skip(1)
.subscribe { state ->
if (connectingStatusDialogRef?.get()?.isShowing != true) return@subscribe
when {
state.isConnecting() -> Unit
state.isConnected() -> {
scheduleDismissConnectingDialogWithMinDuration(shownAt)
}
state.isDisconnected() -> {
if (state.exception != null) {
scheduleDismissConnectingDialogWithMinDuration(shownAt) {
showConnectionFailedDialog(trimmedHost, state.exception)
}
} else {
scheduleDismissConnectingDialogWithMinDuration(shownAt)
}
}
}
}
devPlugin
.connectToRemoteServer(context, trimmedHost)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ Pref.setServerAddress(trimmedHost) },
{ e ->
// Do not toast here; show failure dialog.
// zh-CN: 不在这里 toast; 改为失败对话框.
scheduleDismissConnectingDialogWithMinDuration(shownAt) {
showConnectionFailedDialog(trimmedHost, e)
}
},
)
}
/**
* Show connecting status dialog if current connection state is CONNECTING.
* zh-CN: 若当前连接状态为 CONNECTING, 则显示 "正在连接" 状态对话框.
*/
fun showConnectingStatusDialogIfConnecting(): Boolean {
val state = runCatching { JsonSocketClient.cxnState.value }.getOrNull()
?: return false
if (!state.isConnecting()) return false
if (isContextInvalidForDialog()) return false
val host = (lastConnectingHost ?: Pref.getServerAddress()).trim()
if (host.isEmpty()) return false
// Re-show connecting dialog without starting a new connection attempt.
// zh-CN: 仅重新显示连接中对话框, 不发起新的连接尝试.
showConnectingStatusDialogOnly(host)
return true
}
private fun showConnectingStatusDialogOnly(host: String) {
dismissConnectingStatusDialogSilently()
dismissConnectionFailedDialogSilently()
val shownAt = System.currentTimeMillis()
val statusDialog = MaterialDialog.Builder(context)
.title(R.string.text_connecting)
.content(context.getString(R.string.text_connecting_to_host, host))
.neutralText(R.string.dialog_button_amend_host_address)
.neutralColorRes(R.color.dialog_button_hint)
.onNeutral { d, _ ->
d.dismiss()
// Amend should interrupt immediately to avoid stale timeouts.
// zh-CN: 修正应立即中断连接, 避免后续超时回调.
interruptConnectionAndMarkNormallyClosed()
dismissConnectingStatusDialogSilently()
inputRemoteHost(isAutoConnect = false, prefill = host)
}
.positiveText(R.string.dialog_button_interrupt_connection)
.positiveColorRes(R.color.dialog_button_caution)
.onPositive { d, _ ->
// Treat as user interrupt: stop current attempt and mark normally closed.
// zh-CN: 视为用户中止: 停止当前尝试并标记为正常关闭.
interruptConnectionAndMarkNormallyClosed()
d.dismiss()
dismissConnectingStatusDialogSilently()
}
.cancelable(false)
.autoDismiss(false)
.show()
connectingStatusDialogRef = WeakReference(statusDialog)
// Continue resolving domain to IP if needed.
// zh-CN: 如有需要继续解析域名为 IP.
startResolveHostIpIfNeeded(host)
// Keep dialog in sync with state changes.
// zh-CN: 让对话框随状态变化自动关闭/弹失败.
connectingStatusDisposable = JsonSocketClient.cxnState
.observeOn(AndroidSchedulers.mainThread())
.skip(1)
.subscribe { s: DevPluginService.State ->
if (connectingStatusDialogRef?.get()?.isShowing != true) return@subscribe
when {
s.isConnecting() -> Unit
s.isConnected() -> {
scheduleDismissConnectingDialogWithMinDuration(shownAt)
}
s.isDisconnected() -> {
if (s.exception != null) {
scheduleDismissConnectingDialogWithMinDuration(shownAt) {
showConnectionFailedDialog(host, s.exception)
}
} else {
scheduleDismissConnectingDialogWithMinDuration(shownAt)
}
}
}
}
}
private fun validateAndConnectToRemoteServer(dialog: MaterialDialog) {
if (isContextInvalidForDialog()) {
dismissConnectionDialogSilently()
return
}
val input = dialog.inputEditText?.text?.toString() ?: ""
val input = dialog.inputEditText?.text?.toString()?.trim() ?: ""
if (input.isEmpty()) {
showSnack(dialog, R.string.error_ip_address_should_not_be_empty)
return
}
// If it looks like IPv4, keep strict validation; otherwise allow domain/IPv6 with minimal checks.
// zh-CN: 若看起来像 IPv4, 保持严格校验; 否则允许 域名/IPv6 并仅做最小检查.
if (shouldUseIpv4SmartFilter(input) && !rexValidIp.matches(input)) {
showSnack(dialog, R.string.error_invalid_ip_address)
return
}
// Enforce bracketed form for IPv6 with port: [ipv6]:port (Option 1A).
// zh-CN: 强制 IPv6 带端口使用方括号形式: [ipv6]:port (选项 1A).
val colonCount = input.count { it == ':' }
val hasBracket = input.startsWith("[") && input.contains("]")
val looksLikeIpv6 = colonCount >= 2
val triesToSpecifyPortWithoutBracket = looksLikeIpv6 && !hasBracket && input.lastIndexOf(':') in 1 until input.lastIndex
if (triesToSpecifyPortWithoutBracket) {
showSnack(dialog, R.string.error_ipv6_port_requires_brackets)
return
}
val isInHistory = { JsonSocketClient.serverAddressHistories.contains(input) }
val isPotentiallyInvalid = { POTENTIALLY_INVALID_IP_ADRESS_LIST_FOR_REMOTE_SERVER.any { input.matches(it) } }
fun connectToServer() = devPlugin
.connectToRemoteServer(context, input)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ Pref.setServerAddress(input) },
onConnectionException,
)
.also {
runOnMainThread {
runCatching { dialog.dismiss() }
connectionDialogRef = null
}
}
if (!rexValidIp.matches(input)) {
when (input.isEmpty()) {
true -> showSnack(dialog, R.string.error_ip_address_should_not_be_empty)
else -> showSnack(dialog, R.string.error_invalid_ip_address)
}
return
}
when {
!isInHistory() && isPotentiallyInvalid() -> NotAskAgainDialog.Builder(context)
.title(R.string.text_prompt)
.content(context.getString(R.string.text_ip_address_may_be_invalid_for_server_connection, input))
.widgetThemeColor()
.negativeText(R.string.dialog_button_quit_connecting)
.negativeText(R.string.dialog_button_abandon)
.negativeColorRes(R.color.dialog_button_default)
.positiveText(R.string.dialog_button_continue_connecting)
.positiveText(R.string.dialog_button_continue)
.positiveColorRes(R.color.dialog_button_caution)
.onPositive { _, _ -> connectToServer() }
.onPositive { _, _ -> connectToServerWithStatus(input, dialog) }
.cancelable(false)
.show() ?: connectToServer()
else -> connectToServer()
.show() ?: connectToServerWithStatus(input, dialog)
else -> connectToServerWithStatus(input, dialog)
}
}
@@ -321,20 +584,36 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
}
}
private fun isRepeatedCharacter(source: CharSequence, nearest: Char, regex: String): Boolean {
return Regex(regex).matches(nearest.toString()) && Regex(regex).matches(source)
}
private fun startResolveHostIpIfNeeded(host: String) {
val raw = host.trim()
private fun triggerRepeatedCharacter(dialog: MaterialDialog, source: CharSequence, nearest: Char) = when {
isRepeatedCharacter(source, nearest, REGEX_DOT) -> {
showSnack(dialog, R.string.error_repeated_dot_symbol)
true
}
isRepeatedCharacter(source, nearest, REGEX_COLON) -> {
showSnack(dialog, R.string.error_repeated_colon_symbol)
true
}
else -> false
// Skip literals quickly.
// zh-CN: 快速跳过字面量.
if (raw.any { it.isLetter() }.not() && raw.count { it == '.' } == 3) return
if (raw.startsWith("[") && raw.contains("]")) return
if (raw.count { it == ':' } >= 2) return
Observable
.fromCallable {
// Resolve domain to IP in background.
// zh-CN: 在后台将域名解析为 IP.
InetAddress.getByName(raw).hostAddress
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ ip ->
val dialog = connectingStatusDialogRef?.get() ?: return@subscribe
if (!dialog.isShowing) return@subscribe
val decorated = "$raw [$ip]"
// Update status dialog content.
// zh-CN: 更新状态对话框内容.
dialog.setContent(context.getString(R.string.text_connecting_to_host, decorated))
// Update drawer subtitle via subject.
// zh-CN: 通过 subject 更新抽屉子标题.
devPlugin.clientConnectionIpAddress.onNext("$decorated ...")
}, Observers.emptyConsumer())
}
companion object {

View File

@@ -13,10 +13,10 @@ import org.autojs.autojs.core.eventloop.EventEmitter
import org.autojs.autojs.core.graphics.ScriptCanvas
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.autojs.autojs.util.KotlinUtils.ifNull
import org.mozilla.javascript.BaseFunction
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
/**
* Created by Stardust on Mar 16, 2018.
@@ -30,7 +30,28 @@ class JsCanvasView : TextureView, TextureView.SurfaceTextureListener {
@Volatile
private var mDrawing = true
private val mEventEmitter: EventEmitter by lazy { EventEmitter(mScriptRuntime.bridges) }
private var mDrawingThreadPool: ExecutorService? = null
// Track the draw loop task to allow cancellation.
// zh-CN: 跟踪绘制循环任务, 以便支持取消.
@Volatile
private var mDrawFuture: Future<*>? = null
// Prevent starting draw loop multiple times.
// zh-CN: 防止重复启动绘制循环.
@Volatile
private var mDrawLoopStarted = false
// Serialize surface lock/unlock with surface destroy to avoid native crash.
// zh-CN: 串行化 surface 的 lock/unlock 与 destroy, 避免 native 崩溃.
private val mSurfaceLock = Any()
// Track whether surface is alive/available for drawing.
// zh-CN: 标记 surface 是否处于可用于绘制的存活状态.
@Volatile
private var mSurfaceAlive = false
@Volatile
private var mTimePerDraw = (1000 / 30).toLong()
@@ -56,35 +77,86 @@ class JsCanvasView : TextureView, TextureView.SurfaceTextureListener {
@Synchronized
private fun performDraw() {
::mDrawingThreadPool.ifNull {
Executors.newCachedThreadPool()
}.run {
execute {
var canvas: Canvas? = null
var time = SystemClock.uptimeMillis()
val scriptCanvas = ScriptCanvas(mScriptRuntime)
try {
while (mDrawing) {
canvas = lockCanvas()
// Ensure single-thread executor and keep the reference.
// zh-CN: 确保使用单线程执行器并保存引用.
if (mDrawingThreadPool == null) {
mDrawingThreadPool = Executors.newSingleThreadExecutor()
}
// Do not start draw loop repeatedly.
// zh-CN: 不要重复启动绘制循环.
if (mDrawLoopStarted) {
return
}
mDrawLoopStarted = true
val executor = mDrawingThreadPool ?: return
mDrawFuture = executor.submit {
var canvas: Canvas? = null
var time = SystemClock.uptimeMillis()
val scriptCanvas = ScriptCanvas(mScriptRuntime)
try {
while (mDrawing) {
// Exit quickly if surface is not available.
// zh-CN: 如果 surface 不可用, 尽快退出循环.
if (!mSurfaceAlive || !isAvailable) {
break
}
// Serialize lockCanvas/draw/unlockCanvasAndPost with surface destroy.
// zh-CN: 将 lockCanvas/draw/unlockCanvasAndPost 与 surface destroy 串行化.
synchronized(mSurfaceLock) {
if (!mSurfaceAlive || !isAvailable || !mDrawing) {
return@synchronized
}
try {
canvas = lockCanvas()
} catch (t: Throwable) {
// lockCanvas may throw when surface is being destroyed.
// zh-CN: surface 正在销毁时 lockCanvas 可能抛出异常.
canvas = null
mDrawing = false
return@synchronized
}
if (canvas == null) {
return@synchronized
}
scriptCanvas.setCanvas(canvas)
emit("draw", scriptCanvas, this@JsCanvasView)
if (canvas != null) {
try {
unlockCanvasAndPost(canvas)
} catch (t: Throwable) {
// Guard against device-specific native crash.
// zh-CN: 防御设备特定的 native 崩溃风险.
mDrawing = false
return@synchronized
} finally {
canvas = null
}
val dt = mTimePerDraw - (SystemClock.uptimeMillis() - time)
if (dt > 0) {
sleep(dt)
}
time = SystemClock.uptimeMillis()
}
} catch (e: Exception) {
mScriptRuntime.exit(e)
mDrawing = false
} finally {
if (canvas != null) {
unlockCanvasAndPost(canvas)
val dt = mTimePerDraw - (SystemClock.uptimeMillis() - time)
if (dt > 0) {
sleep(dt)
}
time = SystemClock.uptimeMillis()
}
} catch (e: Exception) {
mScriptRuntime.exit(e)
mDrawing = false
} finally {
// Mark as stopped so it can be started again if needed.
// zh-CN: 标记为已停止, 以便必要时允许再次启动.
mDrawLoopStarted = false
if (canvas != null) {
runCatching { unlockCanvasAndPost(canvas) }
}
}
}
@@ -96,7 +168,6 @@ class JsCanvasView : TextureView, TextureView.SurfaceTextureListener {
} catch (e: InterruptedException) {
throw ScriptInterruptedException(e)
}
}
override fun onWindowVisibilityChanged(visibility: Int) {
@@ -162,6 +233,9 @@ class JsCanvasView : TextureView, TextureView.SurfaceTextureListener {
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
// Mark surface as alive before starting draw loop.
// zh-CN: 在启动绘制循环前将 surface 标记为存活.
mSurfaceAlive = true
performDraw()
Log.d(LOG_TAG, "onSurfaceTextureAvailable: ${this}, width = $width, height = $height")
}
@@ -169,8 +243,23 @@ class JsCanvasView : TextureView, TextureView.SurfaceTextureListener {
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
mDrawing = false
mDrawingThreadPool?.shutdown()
// Stop draw loop and block until current lockCanvas/unlockCanvasAndPost critical section finishes.
// zh-CN: 停止绘制循环, 并阻塞等待当前 lockCanvas/unlockCanvasAndPost 临界区结束.
synchronized(mSurfaceLock) {
mSurfaceAlive = false
mDrawing = false
}
// Do NOT interrupt native drawing calls aggressively.
// zh-CN: 不要对 native 绘制调用进行激进的中断.
runCatching { mDrawFuture?.cancel(false) }
mDrawFuture = null
// Shutdown executor gracefully.
// zh-CN: 平滑关闭执行器.
runCatching { mDrawingThreadPool?.shutdown() }
mDrawingThreadPool = null
Log.d(LOG_TAG, "onSurfaceTextureDestroyed: $this")
return true
}
@@ -186,5 +275,4 @@ class JsCanvasView : TextureView, TextureView.SurfaceTextureListener {
fun defaultMaxListeners(): Int = EventEmitter.defaultMaxListeners()
}
}

View File

@@ -0,0 +1,147 @@
package org.autojs.autojs.permission
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.provider.Settings
import androidx.core.net.toUri
import org.autojs.autojs.ui.main.drawer.PermissionItemHelper
import org.autojs.autojs.util.RomUtils
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs6.R
/**
* Created by SuperMonster003 on Jan 16, 2026.
*/
class VivoBackgroundPopupPermission(
override val context: Context,
) : PermissionItemHelper {
override fun has(): Boolean {
if (!RomUtils.isVivo()) return true
return getVivoBgStartPermissionStatus(context) == STATE_ALLOWED
}
override fun request(): Boolean = config()
override fun revoke(): Boolean = config()
/**
* Open OEM settings page for this permission.
* zh-CN: 打开此权限对应的厂商设置页面.
*/
fun config(): Boolean {
if (!RomUtils.isVivo()) {
return openAppDetails()
}
val pkg = context.packageName
val intents = listOf(
// Vivo permission detail page (commonly works on many models).
// zh-CN: Vivo 权限详情页(较多机型可用).
Intent()
.setClassName(
"com.vivo.permissionmanager",
"com.vivo.permissionmanager.activity.SoftPermissionDetailActivity",
)
.setAction("secure.intent.action.softPermissionDetail")
.putExtra("packagename", pkg)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
// Older models may use PurviewTabActivity.
// zh-CN: 老机型可能使用 PurviewTabActivity.
Intent()
.setClassName(
"com.vivo.permissionmanager",
"com.vivo.permissionmanager.activity.PurviewTabActivity",
)
.putExtra("packagename", pkg)
.putExtra("tabId", "1")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
// Some ROMs expose a background start manager page.
// zh-CN: 部分 ROM 暴露了后台启动管理页.
Intent()
.setClassName(
"com.vivo.permissionmanager",
"com.vivo.permissionmanager.activity.BgStartUpManagerActivity",
)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
return tryStartActivities(intents) || openAppDetails()
}
private fun openAppDetails(): Boolean = Intent()
.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData("package:${context.packageName}".toUri())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.let { tryStartActivity(it) }
/**
* Query vivo PermissionProvider "start_bg_activity" table.
* zh-CN: 查询 vivo 权限 Provider 的 "start_bg_activity" 表.
*/
@SuppressLint("Range")
private fun getVivoBgStartPermissionStatus(context: Context): Int {
val uri = "content://com.vivo.permissionmanager.provider.permission/start_bg_activity".toUri()
val selection = "pkgname = ?"
val selectionArgs = arrayOf(context.packageName)
var state = STATE_DENIED
runCatching {
context.contentResolver.query(uri, null, selection, selectionArgs, null)?.use { c ->
if (c.moveToFirst()) {
val idx = c.getColumnIndex(COLUMN_CURRENT_STATE)
if (idx >= 0) {
state = c.getInt(idx)
}
}
}
}.onFailure {
it.printStackTrace()
// Keep default denied on query failure.
// zh-CN: 查询失败时保持默认无权限.
}
return state
}
// /**
// * Check whether this looks like a vivo/iQOO device.
// * zh-CN: 判断是否为 vivo/iQOO 设备(经验判断).
// */
// private fun isVivoLikeDevice(): Boolean {
// val brand = (Build.BRAND ?: "").lowercase()
// val manufacturer = (Build.MANUFACTURER ?: "").lowercase()
// return manufacturer == "vivo" || brand == "vivo" || brand == "iqoo"
// }
private fun tryStartActivities(intents: List<Intent>): Boolean {
for (i in intents) {
if (tryStartActivity(i)) return true
}
return false
}
private fun tryStartActivity(i: Intent): Boolean = runCatching {
val pm = context.packageManager
i.resolveActivity(pm) ?: return@runCatching false
context.startActivity(i)
true
}.onFailure {
it.printStackTrace()
ViewUtils.showToast(context, R.string.text_failed)
}.getOrDefault(false)
private companion object {
private const val STATE_ALLOWED = 0
private const val STATE_DENIED = 1
private const val COLUMN_CURRENT_STATE = "currentstate"
}
}

View File

@@ -0,0 +1,132 @@
package org.autojs.autojs.permission
import android.app.AppOpsManager
import android.content.Context
import android.content.Intent
import android.os.Process
import android.provider.Settings
import androidx.core.net.toUri
import org.autojs.autojs.ui.main.drawer.PermissionItemHelper
import org.autojs.autojs.util.RomUtils
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs6.R
/**
* Created by SuperMonster003 on Jan 16, 2026.
*/
class XiaomiBackgroundPopupPermission(
override val context: Context,
) : PermissionItemHelper {
override fun has(): Boolean {
if (!RomUtils.isMiui()) return true
return isMiuiBgStartPermissionGranted(context)
}
override fun request(): Boolean = config()
override fun revoke(): Boolean = config()
/**
* Open OEM settings page for this permission.
* zh-CN: 打开此权限对应的厂商设置页面.
*/
fun config(): Boolean {
if (!RomUtils.isMiui()) {
return openAppDetails()
}
val pkg = context.packageName
val intents = listOf(
// MIUI 8+ commonly uses this activity.
// zh-CN: MIUI 8+ 常见使用此 Activity.
Intent("miui.intent.action.APP_PERM_EDITOR")
.setClassName(
"com.miui.securitycenter",
"com.miui.permcenter.permissions.PermissionsEditorActivity",
)
.putExtra("extra_pkgname", pkg)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
// Some MIUI versions use AppPermissionsEditorActivity.
// zh-CN: 部分 MIUI 版本使用 AppPermissionsEditorActivity.
Intent("miui.intent.action.APP_PERM_EDITOR")
.setClassName(
"com.miui.securitycenter",
"com.miui.permcenter.permissions.AppPermissionsEditorActivity",
)
.putExtra("extra_pkgname", pkg)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
return tryStartActivities(intents) || openAppDetails()
}
private fun openAppDetails(): Boolean = Intent()
.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData("package:${context.packageName}".toUri())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.let { tryStartActivity(it) }
/**
* Check MIUI "background start activity" app-op (commonly op=10021).
* zh-CN: 检查 MIUI "后台启动界面" 的 AppOps 状态(常见 op=10021).
*/
private fun isMiuiBgStartPermissionGranted(context: Context): Boolean {
val ops = context.getSystemService(Context.APP_OPS_SERVICE) as? AppOpsManager ?: return true
return runCatching {
val op = OP_BACKGROUND_START_ACTIVITY
val method = ops.javaClass.getMethod(
"checkOpNoThrow",
Int::class.javaPrimitiveType,
Int::class.javaPrimitiveType,
String::class.java,
)
val mode = method.invoke(ops, op, Process.myUid(), context.packageName) as? Int
mode == AppOpsManager.MODE_ALLOWED
}.getOrElse {
it.printStackTrace()
// Fail-open to avoid blocking non-MIUI or changed ROM implementations.
// zh-CN: 发生异常时放行, 避免 ROM 变更导致误判并阻塞功能.
true
}
}
// /**
// * Check whether this looks like a Xiaomi/Redmi/POCO device.
// * zh-CN: 判断是否为 Xiaomi/Redmi/POCO 设备(经验判断).
// */
// private fun isXiaomiLikeDevice(): Boolean {
// val brand = (Build.BRAND ?: "").lowercase()
// val manufacturer = (Build.MANUFACTURER ?: "").lowercase()
// return manufacturer == "xiaomi" || brand == "xiaomi" || brand == "redmi" || brand == "poco"
// }
private fun tryStartActivities(intents: List<Intent>): Boolean {
for (i in intents) {
if (tryStartActivity(i)) return true
}
return false
}
private fun tryStartActivity(i: Intent): Boolean = runCatching {
val pm = context.packageManager
i.resolveActivity(pm) ?: return@runCatching false
context.startActivity(i)
true
}.onFailure {
it.printStackTrace()
ViewUtils.showToast(context, R.string.text_failed)
}.getOrDefault(false)
private companion object {
// MIUI app-op code for background start activity (commonly 10021).
// zh-CN: MIUI 后台启动界面常见的 AppOps 编号(通常为 10021).
private const val OP_BACKGROUND_START_ACTIVITY = 10021
}
}

View File

@@ -7,9 +7,9 @@ import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
import org.autojs.autojs.annotation.ScriptInterface
import org.autojs.autojs.core.pref.Pref
import org.autojs.autojs.pluginclient.JsonSocket.HANDSHAKE_TIMEOUT
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.util.NetworkUtils.DEFAULT_IP_ADDRESS
import org.autojs.autojs.util.ThreadUtils
import java.io.File
import java.io.IOException
@@ -21,7 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean
/**
* Created by Stardust on May 11, 2017.
* Modified by SuperMonster003 as of Jan 9, 2026.
* Transformed by SuperMonster003 on Jul 1, 2023.
* Transformed by SuperMonster003 on Jan 15, 2026.
*/
class DevPluginService(val context: Context) {
@@ -52,7 +52,7 @@ class DevPluginService(val context: Context) {
// zh-CN: 发布当前服务端连接数量.
val serverConnectionCount = BehaviorSubject.createDefault(0)
val clientConnectionIpAddress = BehaviorSubject.createDefault(DEFAULT_IP_ADDRESS)
val clientConnectionIpAddress = BehaviorSubject.createDefault(Pref.getServerAddress())
@get:ScriptInterface
@Volatile
@@ -134,13 +134,9 @@ class DevPluginService(val context: Context) {
}
try {
var port = Port.PC_SERVER
var ip = host
val i = host.lastIndexOf(':')
if (i > 0 && i < host.length - 1) {
port = host.substring(i + 1).toInt()
ip = host.substring(0, i)
}
val endpoint = parseRemoteEndpoint(host, Port.PC_SERVER)
val ip = endpoint.host
val port = endpoint.port
// Show connecting subtitle immediately.
// zh-CN: 立即显示正在连接的 subtitle.
@@ -193,6 +189,44 @@ class DevPluginService(val context: Context) {
return Observable.empty()
}
// Parse host[:port] supporting domain/IPv4/IPv6.
// zh-CN: 解析 host[:port], 支持 域名/IPv4/IPv6.
private fun parseRemoteEndpoint(input: String, defaultPort: Int): RemoteEndpoint {
val s = input.trim()
// Bracketed IPv6: [addr]:port or [addr]
// zh-CN: 方括号 IPv6: [addr]:port 或 [addr].
if (s.startsWith("[") && s.contains("]")) {
val end = s.indexOf(']')
val hostPart = s.substring(1, end)
val rest = s.substring(end + 1)
if (rest.startsWith(":") && rest.length > 1) {
val port = rest.substring(1).toInt()
return RemoteEndpoint(hostPart, port)
}
return RemoteEndpoint(hostPart, defaultPort)
}
// For non-bracketed input:
// - If there is exactly one ':' -> treat as host:port (domain or IPv4)
// - If there are multiple ':' -> treat as raw IPv6 with no port
// zh-CN:
// - 若仅 1 个 ':' -> 视为 host:port (域名 或 IPv4)
// - 若多个 ':' -> 视为不带端口的 IPv6.
val colonCount = s.count { it == ':' }
if (colonCount == 1) {
val i = s.lastIndexOf(':')
val hostPart = s.substring(0, i)
val portPart = s.substring(i + 1)
val port = portPart.toInt()
return RemoteEndpoint(hostPart, port)
}
return RemoteEndpoint(s, defaultPort)
}
private data class RemoteEndpoint(val host: String, val port: Int)
@AnyThread
fun enableLocalServer(): Observable<JsonSocketServer> {
return Observable

View File

@@ -308,12 +308,26 @@ abstract public class JsonSocket extends Socket {
// Treat "Socket closed" as a normal shutdown path.
// zh-CN: 将 "Socket closed" 视为正常关闭流程.
String message = e.getMessage();
if (message != null && (
message.toLowerCase().contains("socket closed") ||
message.toLowerCase().contains("stream ended unexpectedly")
)) {
if (message != null && message.toLowerCase().contains("socket closed")) {
return;
}
// Treat "Stream ended unexpectedly" as error unless user requested normal close.
// zh-CN: 除非用户主动正常关闭, 否则将 "Stream ended unexpectedly" 视为错误.
if (message != null && message.toLowerCase().contains("stream ended unexpectedly")) {
boolean isNormallyClosed = false;
try {
if (jsonSocket instanceof JsonSocketClient) {
isNormallyClosed = JsonSocketClient.Companion.isClientSocketNormallyClosed();
}
} catch (Throwable ignored) {
/* Ignored. */
}
if (isNormallyClosed) {
return;
}
}
onSocketError(e);
} finally {
if (jsonSocket instanceof JsonSocketClient) {

View File

@@ -14,12 +14,12 @@ import io.reactivex.subjects.BehaviorSubject
import org.autojs.autojs.core.pref.Pref
import org.autojs.autojs.core.pref.Pref.getBoolean
import org.autojs.autojs.core.pref.Pref.putBoolean
import org.autojs.autojs.util.NetworkUtils
import org.autojs.autojs.util.StringUtils.key
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs6.BuildConfig
import org.autojs.autojs6.R
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Socket
import java.net.SocketTimeoutException
import java.util.concurrent.Executors
@@ -59,10 +59,15 @@ class JsonSocketClient(service: DevPluginService?, private val ctx: Context, pri
try {
setStateConnecting()
if (mSocket?.isConnected != true) {
mSocket = Socket(host, port)
// Use connect timeout to avoid long blocking (e.g. DNS/connect stall).
// zh-CN: 使用 connect 超时避免长时间阻塞 (例如 DNS/连接卡住).
mSocket = Socket().apply {
connect(InetSocketAddress(host, port), CONNECT_TIMEOUT)
}
}
} catch (e: IOException) {
e.printStackTrace()
runCatching { onSocketError(e) }
}
}
}
@@ -96,7 +101,7 @@ class JsonSocketClient(service: DevPluginService?, private val ctx: Context, pri
// Clear subtitle on close.
// zh-CN: 关闭连接时清空 subtitle.
service.clientConnectionIpAddress.onNext(NetworkUtils.DEFAULT_IP_ADDRESS)
service.clientConnectionIpAddress.onNext("Socket closed")
}
override fun sayHello() {
@@ -143,7 +148,7 @@ class JsonSocketClient(service: DevPluginService?, private val ctx: Context, pri
// Mark as disconnected when server rejects handshake (e.g. version mismatch).
// zh-CN: 当服务端拒绝握手 (例如版本不匹配) 时, 标记为已断开.
service.clientConnectionIpAddress.onNext(NetworkUtils.DEFAULT_IP_ADDRESS)
service.clientConnectionIpAddress.onNext("Disconnected")
setStateDisconnected(IllegalStateException(errorMessage.asString))
try {
@@ -171,7 +176,7 @@ class JsonSocketClient(service: DevPluginService?, private val ctx: Context, pri
// Fallback: version check failed or invalid hello.
// zh-CN: 兜底: 版本校验失败或 hello 异常.
service.clientConnectionIpAddress.onNext(NetworkUtils.DEFAULT_IP_ADDRESS)
service.clientConnectionIpAddress.onNext("Handshake rejected")
setStateDisconnected(IllegalStateException("Handshake rejected"))
try {
@@ -182,10 +187,10 @@ class JsonSocketClient(service: DevPluginService?, private val ctx: Context, pri
val msg = """
${ctx.getString(R.string.text_vsc_ext_version_not_meet_requirement)}.
${ctx.getString(R.string.text_min_version)}: $requiredVersion
${ctx.getString(R.string.text_current_version)}: ${currentVersion ?: "${ctx.getString(R.string.text_lower_than)} $requiredVersion"}
${ctx.getString(R.string.text_repo_url_of_vscode_vsc_ext)}:
${ctx.getString(R.string.url_github_autojs6_vscode_extension_repo)}
""".trimIndent()
@@ -312,6 +317,10 @@ class JsonSocketClient(service: DevPluginService?, private val ctx: Context, pri
private val TAG = JsonSocketClient::class.java.simpleName
// Connect timeout = handshake timeout + 5 seconds (default).
// zh-CN: 连接超时 = 握手超时 + 5 秒 (默认).
private const val CONNECT_TIMEOUT = HANDSHAKE_TIMEOUT + 5_000
var serverAddressHistories: LinkedHashSet<String>
get() = Pref.getLinkedHashSet(R.string.key_pc_server_address_histories)
private set(value) = Pref.putLinkedHashSet(R.string.key_pc_server_address_histories, value)

View File

@@ -28,8 +28,10 @@ import org.autojs.autojs.permission.MediaProjectionPermission
import org.autojs.autojs.permission.PostNotificationsPermission
import org.autojs.autojs.permission.ShizukuPermission
import org.autojs.autojs.permission.UsageStatsPermission
import org.autojs.autojs.permission.VivoBackgroundPopupPermission
import org.autojs.autojs.permission.WriteSecureSettingsPermission
import org.autojs.autojs.permission.WriteSystemSettingsPermission
import org.autojs.autojs.permission.XiaomiBackgroundPopupPermission
import org.autojs.autojs.pluginclient.DevPluginService
import org.autojs.autojs.pluginclient.JsonSocketClient
import org.autojs.autojs.pluginclient.JsonSocketServer
@@ -50,6 +52,7 @@ import org.autojs.autojs.util.IntentUtils.App.exit
import org.autojs.autojs.util.IntentUtils.App.restart
import org.autojs.autojs.util.NetworkUtils
import org.autojs.autojs.util.NotificationUtils
import org.autojs.autojs.util.RomUtils
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs.util.ViewUtils.MODE
import org.autojs.autojs6.BuildConfig
@@ -104,6 +107,8 @@ open class DrawerFragment : Fragment() {
private lateinit var mUsageStatsPermissionItem: DrawerMenuToggleableItem
private lateinit var mIgnoreBatteryOptimizationsItem: DrawerMenuToggleableItem
private lateinit var mDisplayOverOtherAppsItem: DrawerMenuToggleableItem
private lateinit var mXiaomiBackgroundPopupPermissionItem: DrawerMenuToggleableItem
private lateinit var mVivoBackgroundPopupPermissionItem: DrawerMenuToggleableItem
private lateinit var mWriteSystemSettingsItem: DrawerMenuToggleableItem
private lateinit var mWriteSecuritySettingsItem: DrawerMenuToggleableItem
private lateinit var mProjectMediaAccessItem: DrawerMenuToggleableItem
@@ -158,15 +163,15 @@ open class DrawerFragment : Fragment() {
title = R.string.text_a11y_service,
descriptionRes = R.string.description_accessibility_service,
prefKey = R.string.key_a11y_service,
) {
it.setOnLaunchManagerListener { d ->
).also { item ->
item.setOnLaunchManagerListener { d ->
if (d != null) {
ViewUtils.showSnack(d.view, R.string.text_under_development, 1_200)
} else {
ViewUtils.showToast(mContext, R.string.text_under_development)
}
}
it.setOnLaunchSettingsListener {
item.setOnLaunchSettingsListener {
Intent().apply {
action = Settings.ACTION_ACCESSIBILITY_SETTINGS
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -180,10 +185,8 @@ open class DrawerFragment : Fragment() {
title = R.string.text_foreground_service,
descriptionRes = R.string.description_foreground_service,
prefKey = R.string.key_foreground_service,
) {
it.setOnLaunchSettingsListener {
NotificationUtils.launchChannelSettings(mContext, MainActivityForegroundService::class.java)
}
).setOnLaunchSettingsListener {
NotificationUtils.launchChannelSettings(mContext, MainActivityForegroundService::class.java)
}
mFloatingButtonItem = DrawerMenuToggleableItem(
@@ -212,7 +215,7 @@ open class DrawerFragment : Fragment() {
title = R.string.text_floating_button,
descriptionRes = R.string.description_floating_button,
prefKey = R.string.key_floating_menu_shown,
) { item ->
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as DisplayOverOtherAppsPermission
helper.config()
@@ -227,7 +230,11 @@ open class DrawerFragment : Fragment() {
icon = R.drawable.ic_computer_black_48dp,
title = R.string.text_client_mode,
descriptionRes = R.string.description_client_mode,
).also { mClientModeItem = it }
) { helper ->
// If connecting, show status dialog so user can interrupt.
// zh-CN: 若正在连接, 显示状态对话框以便用户中止连接.
(helper as? JsonSocketClientTool)?.showConnectingStatusDialogIfConnecting() == true
}.also { mClientModeItem = it }
val disposable = Observable
.combineLatest(
@@ -244,20 +251,20 @@ open class DrawerFragment : Fragment() {
}
drawerItem.setCheckedIfNeeded(state.isConnected())
drawerItem.isProgress = state.isConnecting()
state.exception?.let { e ->
state.exception?.let {
drawerItem.subtitle = null
ViewUtils.showToast(mContext, e.message)
}
}
setStateDisposable(disposable)
setOnConnectionException { e: Throwable ->
setOnConnectionException { _: Throwable ->
drawerItem.setCheckedIfNeeded(false)
ViewUtils.showToast(context, getString(R.string.error_connect_to_remote, e.message), true)
}
setOnConnectionDialogDismissed {
drawerItem.setCheckedIfNeeded(false)
}
connectIfNotNormallyClosed()
}
@@ -269,15 +276,16 @@ open class DrawerFragment : Fragment() {
icon = R.drawable.ic_smartphone_black_48dp,
title = R.string.text_server_mode,
descriptionRes = R.string.description_server_mode,
) {
it.setOnLaunchManagerListener { d ->
).also { item ->
item.setOnLaunchManagerListener { d ->
if (d != null) {
ViewUtils.showSnack(d.view, R.string.text_under_development, 1_200)
} else {
ViewUtils.showToast(mContext, R.string.text_under_development)
}
}
}.also { mServerModeItem = it }
mServerModeItem = item
}
val disposable = Observable
.combineLatest(
@@ -317,7 +325,7 @@ open class DrawerFragment : Fragment() {
icon = R.drawable.ic_ali_notification,
title = R.string.text_post_notifications_permission,
descriptionRes = R.string.description_post_notifications,
) { item ->
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as PostNotificationsPermission
helper.config()
@@ -329,7 +337,7 @@ open class DrawerFragment : Fragment() {
icon = R.drawable.ic_ali_notification,
title = R.string.text_notification_access_permission,
descriptionRes = R.string.description_notification_access,
) { item ->
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as NotificationService
helper.config()
@@ -341,7 +349,7 @@ open class DrawerFragment : Fragment() {
icon = R.drawable.ic_database_black_48dp,
title = R.string.text_all_files_access,
descriptionRes = R.string.description_all_files_access,
) { item ->
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as AllFilesAccessPermission
helper.config()
@@ -353,7 +361,7 @@ open class DrawerFragment : Fragment() {
icon = R.drawable.ic_assessment_black_48dp,
title = R.string.text_usage_stats_permission,
descriptionRes = R.string.description_usage_stats_access,
) { item ->
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as UsageStatsPermission
helper.config()
@@ -365,7 +373,7 @@ open class DrawerFragment : Fragment() {
icon = R.drawable.ic_battery_std_black_48dp,
title = R.string.text_ignore_battery_optimizations,
descriptionRes = R.string.description_ignore_battery_optimizations,
) { item ->
).also { item ->
item.setOnLaunchSettingsListener {
Intent().apply {
action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
@@ -379,19 +387,45 @@ open class DrawerFragment : Fragment() {
icon = R.drawable.ic_layers_black_48dp,
title = R.string.text_display_over_other_app,
descriptionRes = R.string.description_display_over_other_app,
) { item ->
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as DisplayOverOtherAppsPermission
helper.config()
}
}
mXiaomiBackgroundPopupPermissionItem = DrawerMenuToggleableItem(
helper = XiaomiBackgroundPopupPermission(mContext),
icon = R.drawable.ic_layers_black_48dp,
title = R.string.text_xiaomi_background_popup_permission,
descriptionRes = R.string.description_background_popup_permission,
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as XiaomiBackgroundPopupPermission
helper.config()
}
item.isHidden = !RomUtils.isMiui()
}
mVivoBackgroundPopupPermissionItem = DrawerMenuToggleableItem(
helper = VivoBackgroundPopupPermission(mContext),
icon = R.drawable.ic_layers_black_48dp,
title = R.string.text_vivo_background_popup_permission,
descriptionRes = R.string.description_background_popup_permission,
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as VivoBackgroundPopupPermission
helper.config()
}
item.isHidden = !RomUtils.isVivo()
}
mWriteSystemSettingsItem = DrawerMenuToggleableItem(
helper = WriteSystemSettingsPermission(mContext),
icon = R.drawable.ic_settings_black_48dp,
title = R.string.text_write_system_settings,
descriptionRes = R.string.description_write_system_settings,
) { item ->
).also { item ->
item.setOnLaunchSettingsListener {
val helper = item.getHelper() as WriteSystemSettingsPermission
helper.config()
@@ -467,14 +501,14 @@ open class DrawerFragment : Fragment() {
title = R.string.text_auto_night_mode,
descriptionRes = R.string.description_auto_night_mode,
prefKey = R.string.key_auto_night_mode_enabled,
) { item ->
item.isHidden = !ViewUtils.AutoNightMode.isFunctional()
).also { item ->
item.setOnLaunchSettingsListener {
Intent().apply {
action = Settings.ACTION_DISPLAY_SETTINGS
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.let { i -> mContext.startActivity(i) }
}
item.isHidden = !ViewUtils.AutoNightMode.isFunctional()
}
mNightModeItem = DrawerMenuToggleableItem(
@@ -504,13 +538,11 @@ open class DrawerFragment : Fragment() {
title = R.string.text_night_mode,
descriptionRes = R.string.description_night_mode,
prefKey = R.string.key_night_mode_enabled,
) { item ->
item.setOnLaunchSettingsListener {
Intent().apply {
action = Settings.ACTION_DISPLAY_SETTINGS
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.let { i -> mContext.startActivity(i) }
}
).setOnLaunchSettingsListener {
Intent().apply {
action = Settings.ACTION_DISPLAY_SETTINGS
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.let { i -> mContext.startActivity(i) }
}
mKeepScreenOnWhenInForegroundItem = DrawerMenuToggleableItem(
@@ -658,6 +690,8 @@ open class DrawerFragment : Fragment() {
mUsageStatsPermissionItem,
mIgnoreBatteryOptimizationsItem,
mDisplayOverOtherAppsItem,
mXiaomiBackgroundPopupPermissionItem,
mVivoBackgroundPopupPermissionItem,
mWriteSystemSettingsItem,
mWriteSecuritySettingsItem,
mProjectMediaAccessItem,
@@ -704,6 +738,8 @@ open class DrawerFragment : Fragment() {
mUsageStatsPermissionItem,
mIgnoreBatteryOptimizationsItem,
mDisplayOverOtherAppsItem,
mXiaomiBackgroundPopupPermissionItem,
mVivoBackgroundPopupPermissionItem,
mWriteSystemSettingsItem,
mWriteSecuritySettingsItem,
mProjectMediaAccessItem,

View File

@@ -2,40 +2,25 @@ package org.autojs.autojs.ui.main.drawer
import com.afollestad.materialdialogs.MaterialDialog
class DrawerMenuDisposableItem : DrawerMenuToggleableItem {
private val helper: SocketItemHelper
constructor(
helper: SocketItemHelper,
icon: Int,
title: Int,
onTitleContainerClickListener: (MaterialDialog.Builder.(menuItem: DrawerMenuToggleableItem) -> Unit)? = null,
) : super(
helper,
icon,
title,
onTitleContainerClickListener = onTitleContainerClickListener,
) {
this.helper = helper
}
constructor(
helper: SocketItemHelper,
icon: Int,
title: Int,
descriptionRes: Int,
onTitleContainerClickListener: (MaterialDialog.Builder.(menuItem: DrawerMenuToggleableItem) -> Unit)? = null,
) : super(
helper,
icon,
title,
descriptionRes,
onTitleContainerClickListener = onTitleContainerClickListener,
) {
this.helper = helper
}
class DrawerMenuDisposableItem(
private val helper: SocketItemHelper,
icon: Int,
title: Int,
descriptionRes: Int,
onTitleContainerClickListener: (MaterialDialog.Builder.(helper: SocketItemHelper) -> Any?)? = null,
) : DrawerMenuToggleableItem(
helper,
icon,
title,
descriptionRes,
onTitleContainerClickListener = listener@{
val listener = onTitleContainerClickListener ?: return@listener false
when (val result = listener(this, helper)) {
is Boolean -> result
is Unit -> false
else -> throw IllegalArgumentException("onTitleContainerClickListener must return Boolean or Unit")
}
},
) {
fun dispose() = helper.dispose()
}

View File

@@ -19,7 +19,7 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
private var mDescription: String? = null
private var mDescriptionDialogBuilderProvider: (() -> MaterialDialog.Builder)? = null
private var mOnTitleContainerClickListener: (MaterialDialog.Builder.(menuItem: DrawerMenuToggleableItem) -> Unit)? = null
private var mOnTitleContainerClickListener: (MaterialDialog.Builder.() -> Boolean)? = null
private var mOnLauncherManagerListener: ((MaterialDialog?) -> Unit)? = null
private var mOnLaunchSettingsListener: ((MaterialDialog) -> Unit)? = null
@@ -31,7 +31,7 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
icon: Int,
title: Int,
descriptionRes: Int? = null,
onTitleContainerClickListener: (MaterialDialog.Builder.(menuItem: DrawerMenuToggleableItem) -> Unit)? = null,
onTitleContainerClickListener: (MaterialDialog.Builder.() -> Any?)? = null,
) : super(icon, title, DEFAULT_PREFERENCE_KEY) {
init(helper, null, descriptionRes, onTitleContainerClickListener)
}
@@ -42,7 +42,7 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
title: Int,
switchOnHintRes: Int? = null,
descriptionRes: Int? = null,
onTitleContainerClickListener: (MaterialDialog.Builder.(menuItem: DrawerMenuToggleableItem) -> Unit)? = null,
onTitleContainerClickListener: (MaterialDialog.Builder.() -> Any?)? = null,
) : super(icon, title, DEFAULT_PREFERENCE_KEY) {
init(helper, switchOnHintRes, descriptionRes, onTitleContainerClickListener)
}
@@ -54,7 +54,7 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
switchOnHintRes: Int? = null,
prefKey: Int = DEFAULT_PREFERENCE_KEY,
descriptionRes: Int? = null,
onTitleContainerClickListener: (MaterialDialog.Builder.(menuItem: DrawerMenuToggleableItem) -> Unit)? = null,
onTitleContainerClickListener: (MaterialDialog.Builder.() -> Any?)? = null,
) : super(icon, title, prefKey) {
init(helper, switchOnHintRes, descriptionRes, onTitleContainerClickListener)
}
@@ -63,7 +63,7 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
itemHelper: DrawerMenuItemHelper,
switchOnHintRes: Int?,
descriptionRes: Int?,
onTitleContainerClickListener: (MaterialDialog.Builder.(menuItem: DrawerMenuToggleableItem) -> Unit)? = null,
onTitleContainerClickListener: (MaterialDialog.Builder.() -> Any?)? = null,
) {
mItemHelper = itemHelper
switchOnHintRes?.let { content = itemHelper.context.getString(it) }
@@ -110,7 +110,14 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
}
}
mOnTitleContainerClickListener = onTitleContainerClickListener
mOnTitleContainerClickListener = listener@{
val listener = onTitleContainerClickListener ?: return@listener false
when (val result = listener(this)) {
is Boolean -> result
is Unit -> false
else -> throw IllegalArgumentException("onTitleContainerClickListener must return Boolean or Unit")
}
}
}
fun setOnNotifyItemChangedListener(listener: ((DrawerMenuItem) -> Unit)?) {
@@ -129,7 +136,10 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
fun onTitleContainerClick() {
if (isContextInvalidForDialog()) return
val builder = mDescriptionDialogBuilderProvider?.invoke() ?: return
mOnTitleContainerClickListener?.invoke(builder, this)
val handled = mOnTitleContainerClickListener?.invoke(builder) == true
if (handled) return
runCatching { builder.show() }
}
@@ -141,11 +151,11 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
}.getOrDefault(false)
}
fun setOnLaunchManagerListener(onClickListener: (MaterialDialog?) -> Unit) {
fun setOnLaunchManagerListener(onClickListener: (MaterialDialog?) -> Unit) = also {
mOnLauncherManagerListener = onClickListener
}
fun setOnLaunchSettingsListener(onClickListener: (MaterialDialog) -> Unit) {
fun setOnLaunchSettingsListener(onClickListener: (MaterialDialog) -> Unit) = also {
mOnLaunchSettingsListener = onClickListener
}

View File

@@ -4,10 +4,11 @@ package org.autojs.autojs.util
import android.app.AppOpsManager
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Process
import android.provider.Settings
import androidx.core.net.toUri
import ezy.assist.compat.RomUtil
import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.core.pref.Language
import java.io.BufferedReader
@@ -55,7 +56,7 @@ object RomUtils {
* 判断 Vivo 后台弹出界面状态. 1: 无权限; 0: 有权限.
*/
private fun getVivoBgStartPermissionStatus(context: Context): Int {
val uri = Uri.parse("content://com.vivo.permissionmanager.provider.permission/start_bg_activity")
val uri = "content://com.vivo.permissionmanager.provider.permission/start_bg_activity".toUri()
val selection = "pkgname = ?"
val selectionArgs = arrayOf(context.packageName)
var state = 1
@@ -95,6 +96,8 @@ object RomUtils {
fun isFlyme() = Build.DISPLAY.lowercase(Language.getPrefLanguage().locale).contains("flyme")
fun isQiku() = RomUtil.isQiku()
private fun getSystemProperty(propName: String): String = try {
BufferedReader(
InputStreamReader(

View File

@@ -74,6 +74,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">يسمح إذن \"إدارة جميع الملفات\" (أو \"الوصول إلى جميع الملفات\") لـ AutoJs6 بـ [ إنشاء / قراءة / تعديل / حذف ] الملفات مباشرةً عبر مسارات الملفات العادية ضمن مساحة التخزين المشتركة، مما يتيح للسكربتات الوصول إلى \"Internal Storage\" ويُمكّن مدير الملفات من عرض الملفات وإدارتها بشكل صحيح.\n\nعلى أجهزة Android 11+، تُعد هذه الطريقة الأساسية لتحقيق قدرة القراءة والكتابة على كامل التخزين.</string>
<string name="description_app_language_preference">هذا التفضيل لتغيير لغة العرض لـ AutoJs6 ، بما في ذلك رسائل الاستثناء من تشغيل البرامج النصية.\n\nملاحظة: قد تكون هناك حاجة إلى إعادة تشغيل التطبيق لجعل اللغة تطبيقها كما هو متوقع.</string>
<string name="description_auto_night_mode">عند تفعيل الوضع الليلي التلقائي، سيقوم AutoJs6 بالتبديل تلقائيًا إلى الوضع الليلي وفقًا لإعدادات النظام.\n\nNote: مفتاح الوضع الليلي التلقائي ومفتاح الوضع الليلي مرتبطان ويؤثر كل منهما في الآخر.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">يتيح إذن \"النوافذ المنبثقة في الخلفية\" (ويُسمّى أيضًا \"تشغيل الواجهة من الخلفية\" أو \"إظهار الواجهة في الخلفية\") لـ AutoJs6، حتى عندما يكون التطبيق في الخلفية أو دون واجهة مرئية، تشغيل Activity أو فتح صفحة إعدادات محددة. وهو مناسب لسيناريوهات مثل [ فتح واجهة المستخدم عند تشغيل مهمة مجدولة / استئناف التفاعل بعد قفل الشاشة أو وضع السكون / فتح صفحة إعدادات السكربت عبر إشعار أو اختصار ].\n\nNote: على أنظمة مثل Xiaomi (MIUI/HyperOS) و Vivo (OriginOS/Funtouch OS)، قد يكون هذا الإذن مُعطّلًا افتراضيًا. عند عدم منحه، قد يمنع النظام محاولات السكربت لفتح صفحات من الخلفية، وقد يظهر ذلك على شكل [ عدم استجابة / تنفيذ في الخلفية دون إظهار واجهة / فشل الانتقال ].\n\nحتى بعد منحه، قد تظل هناك سياسات أخرى للنظام تؤثر عليه مثل [ تحسين البطارية / قيود التشغيل التلقائي / تجميد الخلفية / وضع الاستعداد للتطبيق ]. يُنصح بضبطه مع الأذونات ذات الصلة أو إعدادات القائمة البيضاء.</string>
<string name="description_change_working_dir_preference">قم بتغيير مسار الدليل الذي يحتوي على البرامج النصية</string>
<string name="description_check_for_updates_preference">يحصل AutoJs6 على التحديثات وتنزيلها من GitHub.</string>
<string name="description_client_mode">يُستخدم وضع العميل لتمكين AutoJs6 من الاتصال بشكل نشط بخادم بعيد لتنفيذ [ نقل السكربتات / طباعة السجلات / التحكم عن بُعد ].\n\nعادةً ما يلزم أن يكون الجهاز والخادم ضمن نفس الشبكة المحلية (LAN) أو ضمن بيئة شبكة يمكنهما الوصول فيها إلى بعضهما البعض.</string>
@@ -111,7 +112,9 @@
<string name="description_version_histories_preference">عرض سجل إصدارات AutoJs6 على GitHub وإحصاءات الفئات الرئيسية.</string>
<string name="description_write_secure_settings">إعدادات النظام الآمنة ، التي تحتوي على تفضيلات النظام التي يمكن أن تقرأها التطبيقات ولكن لا يُسمح لها بالكتابة.\nهذه هي لتفضيلات يجب على المستخدم تعديلها بشكل صريح من خلال واجهة المستخدم لتطبيق النظام.\nمع إذن إعدادات النظام الآمن ، يمكن للتطبيقات العادية تعديل الإعدادات الآمنة مباشرة (مثل خدمة إمكانية الوصول).</string>
<string name="description_write_system_settings">يسمح إذن \"تعديل إعدادات النظام\" لـ AutoJs6 بتعديل بعض إعدادات النظام، مما يتيح للسكربتات تعديل إعدادات مثل [ سطوع الشاشة / التدوير التلقائي / مهلة إيقاف الشاشة ].</string>
<string name="dialog_button_abandon">تخلَّ</string>
<string name="dialog_button_advanced_settings">متقدم</string>
<string name="dialog_button_amend_host_address">تصحيح العنوان</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">يلغي</string>
<string name="dialog_button_cancel_download">يلغي</string>
@@ -119,7 +122,6 @@
<string name="dialog_button_confirm">نعم</string>
<string name="dialog_button_connect">اتصال</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">@string/dialog_button_continue</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">بادئة افتراضية</string>
<string name="dialog_button_details">تفاصيل</string>
@@ -129,12 +131,12 @@
<string name="dialog_button_file_information">معلومات الملف</string>
<string name="dialog_button_history">تاريخ</string>
<string name="dialog_button_ignore_current_update">يتجاهل</string>
<string name="dialog_button_interrupt_connection">إيقاف الاتصال</string>
<string name="dialog_button_join_group">إنضم للمجموعة</string>
<string name="dialog_button_manager">المدير</string>
<string name="dialog_button_more">أكثر</string>
<string name="dialog_button_open_color_palette">فتح لوحة الألوان</string>
<string name="dialog_button_quit">يترك</string>
<string name="dialog_button_quit_connecting">@string/dialog_button_quit</string>
<string name="dialog_button_remove">إزالة</string>
<string name="dialog_button_retrieve">جلب</string>
<string name="dialog_button_retry">إعادة المحاولة</string>
@@ -214,6 +216,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">يجب أن يتبع القولون عنوان IP صالحًا</string>
<string name="error_compass_cannot_be_null">لا يمكن أن تكون البوصلة لاغية</string>
<string name="error_connect_to_remote">لا يمكن الاتصال بالخادم البعيد: %s</string>
<string name="error_connection_failed_with_host">فشل الاتصال بـ \"%1$s\".</string>
<string name="error_connection_timed_out">انتهت مهلة الاتصال (%d مللي ثانية)</string>
<string name="error_continuation_resume_called_without_suspend">يجب استدعاء الطريقة السيرة الذاتية() بعد تعليق()</string>
<string name="error_continuation_suspend_called_more_than_once">يجب استدعاء طريقة تعليق() مرة واحدة فقط</string>
<string name="error_corresponding_github_release_may_not_published">قد لم يتم نشر إصدار GitHub المقابل بعد</string>
@@ -267,6 +271,7 @@
<string name="error_invalid_rest_compass">بوصلة راحة غير صالحة: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">عنصر خريطة محدد غير صالح: {%s: %s (%s)}</string>
<string name="error_ip_address_should_not_be_empty">لا يمكن أن يكون عنوان IP فارغًا</string>
<string name="error_ipv6_port_requires_brackets">يجب تحديد منفذ IPv6 بصيغة \"[ipv6]:port\".</string>
<string name="error_max_listeners_exceeded">تم تجاوز مستمعي أقصى: %s</string>
<string name="error_may_not_have_root_access_to_run_auto_file">قد لا يكون لدى AutoJs6 ملف الجذر لتشغيل ملف \"auto\"</string>
<string name="error_method_called_with_null_argument" formatted="false">%s() دعا مع الوسيطة الفارغة: %s</string>
@@ -339,6 +344,7 @@
<string name="hint_long_click_run_to_debug">انقر الطويل على زر \"تشغيل\" لتصحيح الأخطاء</string>
<string name="hint_loop_delay">تأخير قبل الحلقة</string>
<string name="hint_loop_times">0 للحلقة اللانهائية</string>
<string name="hint_pc_server_address_supported_formats">يتم دعم IPv4 و IPv6 وأسماء النطاقات.</string>
<string name="instruction_install_plugin_from_url">أدخل عنوان URL يشير إلى ملحق (Plugin) بعيد.\nمثال: \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">آخر استخدام: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">فشل خيط \"blob"\</string>
@@ -563,7 +569,10 @@
<string name="text_connect_to_pc">الاتصال بالكمبيوتر</string>
<string name="text_connected">متصل</string>
<string name="text_connected_count_with_colon">متصل: %1$d</string>
<string name="text_connecting">جارٍ الاتصال</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">جارٍ الاتصال بـ \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">لا يمكن إنشاء الاتصال</string>
<string name="text_connection_failed">فشل الاتصال</string>
<string name="text_console">وحدة التحكم</string>
<string name="text_continue">يكمل</string>
<string name="text_copy">ينسخ</string>
@@ -1156,6 +1165,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">اسم الإصدار (جارٍ الحساب...)</string>
<string name="text_view">عرض</string>
<string name="text_view_docs">عرض المستندات</string>
<string name="text_vivo_background_popup_permission">النوافذ المنبثقة في الخلفية</string>
<string name="text_vsc_ext_version_not_meet_requirement">إصدار امتداد VSCode لا يفي بالمتطلبات</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">بانتظار اكتمال معالجة جميع البيانات...</string>
<string name="text_weekly_task">مهمة أسبوعية</string>
@@ -1163,5 +1173,6 @@
<string name="text_working_dir_path">مسار دليل العمل</string>
<string name="text_write_secure_settings">اكتب إعدادات الأمان</string>
<string name="text_write_system_settings">كتابة إعدادات النظام</string>
<string name="text_xiaomi_background_popup_permission">النوافذ المنبثقة في الخلفية</string>
</resources>

View File

@@ -69,6 +69,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">The \"manage all files\" (or \"all files access\") permission allows AutoJs6 to directly [ create / read / modify / delete ] files in shared storage via normal file paths, allowing scripts to access \"Internal Storage\" and enabling the file manager to display and manage files properly.\n\nOn Android 11+ devices, this is the primary way to achieve full file read/write access.</string>
<string name="description_app_language_preference">Preference for changing the display language of AutoJs6, including exception messages from running scripts.\n\nNote: an app restart may be needed to make language applied as expected.</string>
<string name="description_auto_night_mode">When auto night mode is enabled, AutoJs6 will automatically switch night mode based on system settings.\n\nNote: the auto night mode toggle and the night mode toggle are linked and affect each other.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">The \"background pop-up\" permission (also known as \"background activity launch\" or \"launch UI from background\") allows AutoJs6 to proactively start an Activity or open specific settings pages even when the app is in the background or no UI is currently visible. It is useful for scenarios such as [ opening UI when a scheduled task is triggered / resuming interaction after the device is locked or idle / opening a script configuration page from a notification or shortcut ].\n\nNote: on systems like Xiaomi (MIUI/HyperOS) and Vivo (OriginOS/Funtouch OS), this permission may be disabled by default. Without it, attempts to open pages from the background may be blocked by the system, which can appear as [ no response / running in background only without showing UI / navigation failed ].\n\nEven after granting it, the behavior may still be affected by other system policies such as [ battery optimizations / auto-start restrictions / background freezing / app standby ]. Consider adjusting it together with related permissions or whitelist settings.</string>
<string name="description_change_working_dir_preference">Change the path of the directory containing scripts</string>
<string name="description_check_for_updates_preference">AutoJs6 gets and downloads updates from GitHub.</string>
<string name="description_client_mode">Client mode allows AutoJs6 to actively connect to a remote server for [ script transfer / log printing / remote control ].\n\nTypically, the device and the server need to be on the same LAN or on a network where they can reach each other.</string>
@@ -106,7 +107,9 @@
<string name="description_version_histories_preference">View the release version history and key category statistics of AutoJs6 on GitHub.</string>
<string name="description_write_secure_settings">Secure system settings, containing system preferences that applications can read but are not allowed to write.\nThese are for preferences that the user must explicitly modify through the UI of a system app.\nWith secure system settings permission, normal applications can directly modify the secure settings (such as accessibility service).</string>
<string name="description_write_system_settings">The \"write system settings\" permission allows AutoJs6 to modify some system settings, so scripts can change system parameters such as [ screen brightness / auto-rotate / screen timeout ].</string>
<string name="dialog_button_abandon">Abandon</string>
<string name="dialog_button_advanced_settings">Advanced</string>
<string name="dialog_button_amend_host_address">Amend</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">Cancel</string>
<string name="dialog_button_cancel_download">Cancel</string>
@@ -114,7 +117,6 @@
<string name="dialog_button_confirm">OK</string>
<string name="dialog_button_connect">Connect</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">@string/dialog_button_continue</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">Def prefix</string>
<string name="dialog_button_details">Details</string>
@@ -124,12 +126,12 @@
<string name="dialog_button_file_information">File info</string>
<string name="dialog_button_history">History</string>
<string name="dialog_button_ignore_current_update">Ignore</string>
<string name="dialog_button_interrupt_connection">Interrupt</string>
<string name="dialog_button_join_group">Join group</string>
<string name="dialog_button_manager">Manager</string>
<string name="dialog_button_more">More</string>
<string name="dialog_button_open_color_palette">Palette</string>
<string name="dialog_button_quit">Quit</string>
<string name="dialog_button_quit_connecting">@string/dialog_button_quit</string>
<string name="dialog_button_remove">Remove</string>
<string name="dialog_button_retrieve">Retrieve</string>
<string name="dialog_button_retry">Retry</string>
@@ -209,6 +211,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">Colon must follow a valid IP address</string>
<string name="error_compass_cannot_be_null">Compass cannot be null</string>
<string name="error_connect_to_remote">Cannot connect to the remote server: %s</string>
<string name="error_connection_failed_with_host">Failed to connect to \"%1$s\".</string>
<string name="error_connection_timed_out">Connection timed out after %d ms</string>
<string name="error_continuation_resume_called_without_suspend">Method resume() should be called after suspend()</string>
<string name="error_continuation_suspend_called_more_than_once">Method suspend() should be only called once</string>
<string name="error_corresponding_github_release_may_not_published">Corresponding GitHub release may have not been published yet</string>
@@ -262,6 +266,7 @@
<string name="error_invalid_rest_compass">Invalid rest compass: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">Invalid selector map element: { %s: %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">IP address can not be empty</string>
<string name="error_ipv6_port_requires_brackets">IPv6 port must be specified as \"[ipv6]:port\".</string>
<string name="error_max_listeners_exceeded">Max listeners exceeded: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 may not have root access to run \"auto\" file</string>
<string name="error_method_called_with_null_argument" formatted="false">%s() called with null argument: %s</string>
@@ -334,6 +339,7 @@
<string name="hint_long_click_run_to_debug">Long click \"Run\" button to debug</string>
<string name="hint_loop_delay">Delay before loop</string>
<string name="hint_loop_times">0 for infinite loop</string>
<string name="hint_pc_server_address_supported_formats">IPv4, IPv6 and domain are supported.</string>
<string name="instruction_install_plugin_from_url">Enter a URL pointing to a remote plugin address.\nFor example \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">Latest used: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">\"Blob\" thread request failed</string>
@@ -558,7 +564,10 @@
<string name="text_connect_to_pc">Connect to PC</string>
<string name="text_connected">Connected</string>
<string name="text_connected_count_with_colon">Connected: %1$d</string>
<string name="text_connecting">Connecting</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">Connecting to \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">Connection cannot be established</string>
<string name="text_connection_failed">Connection failed</string>
<string name="text_console">Console</string>
<string name="text_continue">Continue</string>
<string name="text_copy">Copy</string>
@@ -1151,6 +1160,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Version name (computing...)</string>
<string name="text_view">View</string>
<string name="text_view_docs">View documents</string>
<string name="text_vivo_background_popup_permission">Background pop-ups</string>
<string name="text_vsc_ext_version_not_meet_requirement">The version of the VSCode extension does not meet the requirements</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">Waiting for all data processing to complete...</string>
<string name="text_weekly_task">Weekly task</string>
@@ -1158,5 +1168,6 @@
<string name="text_working_dir_path">Working directory path</string>
<string name="text_write_secure_settings">Write security settings</string>
<string name="text_write_system_settings">Write system settings</string>
<string name="text_xiaomi_background_popup_permission">Display pop-up windows while running in the background</string>
</resources>

View File

@@ -72,6 +72,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">El permiso \"administrar todos los archivos\" (o \"acceso a todos los archivos\") permite que AutoJs6 [ cree / lea / modifique / elimine ] archivos directamente mediante rutas de archivo normales en el almacenamiento compartido, de modo que los scripts puedan acceder al \"Internal Storage\" y el explorador de archivos pueda mostrar y administrar archivos correctamente.\n\nEn dispositivos con Android 11+, esta es la forma principal de lograr acceso de lectura y escritura a todo el almacenamiento.</string>
<string name="description_app_language_preference">Esta preferencia para cambiar el idioma de visualización de AutoJs6, incluyendo los mensajes de excepción de los scripts en ejecución.\n\nNota: puede ser necesario reiniciar la aplicación para que el idioma se aplique como se espera.</string>
<string name="description_auto_night_mode">Cuando el modo nocturno automático está activado, AutoJs6 cambiará automáticamente al modo nocturno según la configuración del sistema.\n\nNote: el interruptor de modo nocturno automático y el interruptor de modo nocturno están vinculados y se afectan mutuamente.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">El permiso \"ventanas emergentes en segundo plano\" (también llamado \"iniciar interfaz desde segundo plano\" o \"mostrar interfaz en segundo plano\") permite que AutoJs6, incluso cuando la app está en segundo plano o sin interfaz visible, pueda iniciar una Activity o abrir una página de ajustes específica. Es útil para escenarios como [ abrir la UI al activarse una tarea programada / reanudar la interacción tras el bloqueo o la suspensión / abrir la página de configuración de scripts desde una notificación o un acceso directo ].\n\nNote: en sistemas como Xiaomi (MIUI/HyperOS) y Vivo (OriginOS/Funtouch OS), este permiso puede estar desactivado por defecto. Si no se concede, el sistema puede bloquear que un script abra páginas desde segundo plano, lo que puede manifestarse como [ sin respuesta / ejecución en segundo plano sin mostrar interfaz / fallo al navegar ].\n\nIncluso después de concederlo, puede seguir viéndose afectado por otras políticas del sistema, como [ optimización de batería / restricciones de inicio automático / congelación en segundo plano / modo de espera de la aplicación ]. Se recomienda ajustarlo junto con permisos relacionados o listas blancas.</string>
<string name="description_change_working_dir_preference">Cambiar la ruta del directorio que contiene los scripts</string>
<string name="description_check_for_updates_preference">AutoJs6 obtiene y descarga las actualizaciones de GitHub.</string>
<string name="description_client_mode">El modo cliente permite que AutoJs6 se conecte activamente a un servidor remoto para realizar [ transferencia de scripts / impresión de registros / control remoto ].\n\nPor lo general, el dispositivo y el servidor deben estar en la misma red local (LAN) o en una red accesible entre sí.</string>
@@ -109,7 +110,9 @@
<string name="description_version_histories_preference">Ver el historial de versiones publicadas de AutoJs6 en GitHub y las estadísticas de las categorías importantes.</string>
<string name="description_write_secure_settings">Ajustes de seguridad del sistema, que contienen preferencias del sistema que las aplicaciones pueden leer pero no pueden escribir.\nSe trata de preferencias que el usuario debe modificar explícitamente a través de la interfaz de usuario de una aplicación del sistema.\nCon el permiso de configuración segura del sistema, las aplicaciones normales pueden modificar directamente la configuración segura (como el servicio de accesibilidad).</string>
<string name="description_write_system_settings">El permiso \"modificar ajustes del sistema\" permite que AutoJs6 cambie algunos ajustes del sistema, de modo que los scripts puedan modificar parámetros como [ brillo de pantalla / rotación automática / tiempo de espera de pantalla ].</string>
<string name="dialog_button_abandon">Abandonar</string>
<string name="dialog_button_advanced_settings">Avanzado</string>
<string name="dialog_button_amend_host_address">Corregir dirección</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">Cancelar</string>
<string name="dialog_button_cancel_download">Cancelar</string>
@@ -117,7 +120,6 @@
<string name="dialog_button_confirm">OK</string>
<string name="dialog_button_connect">Conectar</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">@string/dialog_button_continue</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">Prefijo def</string>
<string name="dialog_button_details">Detalles</string>
@@ -127,12 +129,12 @@
<string name="dialog_button_file_information">Información del archivo</string>
<string name="dialog_button_history">Historial</string>
<string name="dialog_button_ignore_current_update">Ignorar</string>
<string name="dialog_button_interrupt_connection">Interrumpir conexión</string>
<string name="dialog_button_join_group">Unirse grupo</string>
<string name="dialog_button_manager">Administrador</string>
<string name="dialog_button_more">Más</string>
<string name="dialog_button_open_color_palette">Abrir paleta</string>
<string name="dialog_button_quit">Salir</string>
<string name="dialog_button_quit_connecting">@string/dialog_button_quit</string>
<string name="dialog_button_remove">Eliminar</string>
<string name="dialog_button_retrieve">Obtener</string>
<string name="dialog_button_retry">Reintentar</string>
@@ -212,6 +214,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">Dos puntos deben seguir a una dirección IP válida</string>
<string name="error_compass_cannot_be_null">La brújula no puede ser nula</string>
<string name="error_connect_to_remote">No se puede conectar con el servidor remoto: %s</string>
<string name="error_connection_failed_with_host">No se pudo conectar a \"%1$s\".</string>
<string name="error_connection_timed_out">La conexión agotó el tiempo de espera (%d ms)</string>
<string name="error_continuation_resume_called_without_suspend">El método resume() debe ser llamado después de suspender()</string>
<string name="error_continuation_suspend_called_more_than_once">El método suspender() debería llamarse sólo una vez</string>
<string name="error_corresponding_github_release_may_not_published">Es posible que la versión correspondiente de GitHub no se haya publicado todavía</string>
@@ -265,6 +269,7 @@
<string name="error_invalid_rest_compass">Compás de reposo inválido: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">Elemento de mapa selector no válido: { %s: %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">La dirección IP no puede estar vacía</string>
<string name="error_ipv6_port_requires_brackets">El puerto IPv6 debe especificarse con el formato \"[ipv6]:port\".</string>
<string name="error_max_listeners_exceeded">Max listeners exceeded: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 puede no tener acceso a la raíz para ejecutar el archivo \"auto</string>
<string name="error_method_called_with_null_argument" formatted="false">%s() llamada con argumento nulo: %s</string>
@@ -337,6 +342,7 @@
<string name="hint_long_click_run_to_debug">Haga un clic largo en el botón \"Ejecutar\" para depurar</string>
<string name="hint_loop_delay">Retraso antes del bucle</string>
<string name="hint_loop_times">0 para bucle infinito</string>
<string name="hint_pc_server_address_supported_formats">Se admiten IPv4, IPv6 y dominios.</string>
<string name="instruction_install_plugin_from_url">Introduce una URL que apunte a la dirección de un complemento remoto.\nPor ejemplo, \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">Último uso: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">Hilo \"blob\" fallido</string>
@@ -561,7 +567,10 @@
<string name="text_connect_to_pc">Conectar al PC</string>
<string name="text_connected">Conectado</string>
<string name="text_connected_count_with_colon">Conectados: %1$d</string>
<string name="text_connecting">Conectando</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">Conectando a \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">No se puede establecer la conexión</string>
<string name="text_connection_failed">Conexión fallida</string>
<string name="text_console">Consola</string>
<string name="text_continue">Continuar</string>
<string name="text_copy">Copiar</string>
@@ -1154,6 +1163,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Nombre de la versión (Calculando...)</string>
<string name="text_view">Ver</string>
<string name="text_view_docs">Ver documentos</string>
<string name="text_vivo_background_popup_permission">Ventanas emergentes en segundo plano</string>
<string name="text_vsc_ext_version_not_meet_requirement">La versión de la extensión VSCode no cumple los requisitos</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">Esperando a que se complete el procesamiento de todos los datos...</string>
<string name="text_weekly_task">Tarea semanal</string>
@@ -1161,5 +1171,6 @@
<string name="text_working_dir_path">Ruta del directorio de trabajo</string>
<string name="text_write_secure_settings">Escribir la configuración de seguridad</string>
<string name="text_write_system_settings">Escribir la configuración del sistema</string>
<string name="text_xiaomi_background_popup_permission">Ventanas emergentes en segundo plano</string>
</resources>

View File

@@ -72,6 +72,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">L\'autorisation \"gérer tous les fichiers\" (ou \"accès à tous les fichiers\") permet à AutoJs6 d\'[ créer / lire / modifier / supprimer ] des fichiers directement via des chemins de fichiers classiques dans l\'espace de stockage partagé, afin que les scripts puissent accéder à \"Internal Storage\" et que l\'explorateur de fichiers puisse afficher et gérer les fichiers correctement.\n\nSur les appareils Android 11+, c\'est le principal moyen d\'obtenir un accès lecture/écriture à l\'ensemble du stockage.</string>
<string name="description_app_language_preference">Cette préférence permet de modifier la langue d\'affichage d\'AutoJs6, y compris les messages d\'exception des scripts en cours d\'exécution.\n\nNote : un redémarrage de l\'application peut être nécessaire pour que la langue s\'applique comme prévu.</string>
<string name="description_auto_night_mode">Lorsque le mode nuit automatique est activé, AutoJs6 bascule automatiquement en mode nuit en fonction des paramètres système.\n\nNote: l\'interrupteur de mode nuit automatique et l\'interrupteur de mode nuit sont liés et s\'influencent mutuellement.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">L\'autorisation \"fenêtres contextuelles en arrière-plan\" (également appelée \"démarrer une interface en arrière-plan\" ou \"afficher une interface en arrière-plan\") permet à AutoJs6, même lorsque l\'application est en arrière-plan ou sans interface visible, de démarrer une Activity ou d\'ouvrir une page de paramètres spécifique. Utile dans des scénarios tels que [ ouvrir l\'UI lors du déclenchement d\'une tâche planifiée / reprendre l\'interaction après l\'écran verrouillé ou la mise en veille / ouvrir la page de configuration des scripts via une notification ou un raccourci ].\n\nNote: sur des systèmes comme Xiaomi (MIUI/HyperOS) et Vivo (OriginOS/Funtouch OS), cette autorisation peut être désactivée par défaut. Si elle n\'est pas accordée, le système peut bloquer l\'ouverture de pages depuis l\'arrière-plan par un script, ce qui peut se manifester par [ aucune réaction / exécution en arrière-plan sans interface / échec de navigation ].\n\nMême après l\'avoir accordée, d\'autres politiques système peuvent encore s\'appliquer, telles que [ optimisation de la batterie / restrictions d\'autodémarrage / gel en arrière-plan / mise en veille de l\'application ]. Il est recommandé de l\'ajuster conjointement avec les autorisations associées ou des réglages de liste blanche.</string>
<string name="description_change_working_dir_preference">Changer le chemin du répertoire contenant les scripts</string>.
<string name="description_check_for_updates_preference">AutoJs6 obtient et télécharge les mises à jour depuis GitHub.</string>
<string name="description_client_mode">Le mode client permet à AutoJs6 de se connecter activement à un serveur distant afin d\'effectuer [ transfert de scripts / impression des journaux / contrôle à distance ].\n\nEn général, l\'appareil et le serveur doivent être sur le même réseau local (LAN) ou dans un environnement réseau où ils peuvent s\'atteindre mutuellement.</string>
@@ -109,7 +110,9 @@
<string name="description_version_histories_preference">Consulter l\'historique des versions publiées d\'AutoJs6 sur GitHub ainsi que les statistiques des principales catégories.</string>
<string name="description_write_secure_settings">Paramètres de sécurité du système, contenant les préférences du système que les applications peuvent lire mais ne sont pas autorisées à écrire.\nIl s\'agit des préférences que l\'utilisateur doit explicitement modifier par le biais de l\'interface utilisateur d\'une application système.\nAvec l\'autorisation de paramètres de sécurité du système, les applications normales peuvent directement modifier les paramètres de sécurité (comme le service d\'accessibilité).</string>
<string name="description_write_system_settings">L\'autorisation \"modifier les paramètres système\" permet à AutoJs6 de modifier certains paramètres système, afin que les scripts puissent changer des réglages tels que [ luminosité de l\'écran / rotation automatique / délai d\'extinction de l\'écran ].</string>
<string name="dialog_button_abandon">Abandonner</string>
<string name="dialog_button_advanced_settings">Avancé</string>
<string name="dialog_button_amend_host_address">Corriger l\'adresse</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">Cancel</string>
<string name="dialog_button_cancel_download">Annulation</string>
@@ -117,7 +120,6 @@
<string name="dialog_button_confirm">OK</string>
<string name="dialog_button_connect">Se connecter</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">@string/dialog_button_continue</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">Préfixe def</string>
<string name="dialog_button_details">Details</string>
@@ -127,12 +129,12 @@
<string name="dialog_button_file_information">Infos fichier</string>
<string name="dialog_button_history">Histoire</string>
<string name="dialog_button_ignore_current_update">Ignorer</string>
<string name="dialog_button_interrupt_connection">Interrompre la connexion</string>
<string name="dialog_button_join_group">Joindre groupe</string>
<string name="dialog_button_manager">Gestionnaire</string>
<string name="dialog_button_more">Plus</string>
<string name="dialog_button_open_color_palette">Palette</string>
<string name="dialog_button_quit">Quit</string>
<string name="dialog_button_quit_connecting">@string/dialog_button_quit</string>
<string name="dialog_button_remove">Supprimer</string>
<string name="dialog_button_retrieve">Récupérer</string>
<string name="dialog_button_retry">Retourner</string>
@@ -212,6 +214,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">Les deux points doivent suivre une adresse IP valide</string>
<string name="error_compass_cannot_be_null">Compass ne peut pas être null</string>
<string name="error_connect_to_remote">Impossible de se connecter au serveur distant : %s</string>
<string name="error_connection_failed_with_host">Échec de la connexion à \"%1$s\".</string>
<string name="error_connection_timed_out">Délai de connexion dépassé (%d ms)</string>
<string name="error_continuation_resume_called_without_suspend">La méthode resume() doit être appelée après suspend()</string>.
<string name="error_continuation_suspend_called_more_than_once">La méthode suspend() ne devrait être appelée qu\'une seule fois</string>.
<string name="error_corresponding_github_release_may_not_published">La version GitHub correspondante n\'a peut-être pas encore été publiée</string>.
@@ -265,6 +269,7 @@
<string name="error_invalid_rest_compass">Invalid rest compass : %s</string>
<string name="error_invalid_selector_map_element" formatted="false">Elément de carte de sélecteur non valide : { %s : %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">L\'adresse IP ne peut pas être vide</string>
<string name="error_ipv6_port_requires_brackets">Le port IPv6 doit être spécifié au format \"[ipv6]:port\".</string>
<string name="error_max_listeners_exceeded">Max listeners exceeded : %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 peut ne pas avoir l\'accès root pour exécuter le fichier \"auto\"</string>.
<string name="error_method_called_with_null_argument" formatted="false">%s() appelé avec un argument nul : %s</string>
@@ -337,6 +342,7 @@
<string name="hint_long_click_run_to_debug">Cliquez longuement sur le bouton \"Run\" pour déboguer</string>
<string name="hint_loop_delay">Délai avant boucle</string>
<string name="hint_loop_times">0 pour une boucle infinie</string>
<string name="hint_pc_server_address_supported_formats">IPv4, IPv6 et les noms de domaine sont pris en charge.</string>
<string name="instruction_install_plugin_from_url">Saisissez une URL pointant vers l\'adresse d\'un plugin distant.\nPar exemple \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">Dernière utilisation : %1$s</string>
<string name="logger_ver_history_blob_thread_failure">Échec du thread \"blob\"</string>
@@ -561,7 +567,10 @@
<string name="text_connect_to_pc">Connexion au PC</string>
<string name="text_connected">Connecté</string>
<string name="text_connected_count_with_colon">Connectés : %1$d</string>
<string name="text_connecting" tools:ignore="TypographyEllipsis">Connexion...</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">Connexion à \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">La connexion ne peut pas être établie</string>
<string name="text_connection_failed">Échec de la connexion</string>
<string name="text_console">Console</string>
<string name="text_continue">Continue</string>
<string name="text_copy">Copie</string>
@@ -1154,6 +1163,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Nom de la version (en cours de calcul...)</string>
<string name="text_view">Voir</string>
<string name="text_view_docs">Voir les documents</string>
<string name="text_vivo_background_popup_permission">Fenêtres contextuelles en arrière-plan</string>
<string name="text_vsc_ext_version_not_meet_requirement">La version de l\'extension VSCode ne répond pas aux exigences</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">En attente de la fin du traitement de toutes les données...</string>
<string name="text_weekly_task">Tâche hebdomadaire</string>
@@ -1161,5 +1171,6 @@
<string name="text_working_dir_path">Chemin du répertoire de travail</string>.
<string name="text_write_secure_settings">Écrire les paramètres de sécurité</string>.
<string name="text_write_system_settings">Écrire les paramètres système</string>
<string name="text_xiaomi_background_popup_permission">Fenêtres contextuelles en arrière-plan</string>
</resources>

View File

@@ -73,6 +73,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">\"すべてのファイルを管理\" (または \"すべてのファイルへのアクセス\") 権限により, AutoJs6 は共有ストレージ上で通常のファイルパスを使って直接 [ 作成 / 読み取り / 変更 / 削除 ] を行えます. これによりスクリプトは \"Internal Storage\" にアクセスでき, ファイルマネージャーもファイルを正常に表示・管理できます.\n\nAndroid 11+ では, フルストレージの読み書きを実現する主要な方法です.</string>
<string name="description_app_language_preference">この環境設定は, 実行中のスクリプトからの例外メッセージを含む, AutoJs6 の表示言語を変更するためのものです.\n\n注: 言語を期待通りに適用するには, アプリの再起動が必要な場合があります</string>
<string name="description_auto_night_mode">自動ナイトモードを有効にすると, AutoJs6 はシステム設定に従って自動的にナイトモードへ切り替えます.\n\nNote: 自動ナイトモードのスイッチとナイトモードのスイッチは相互に関連しており, 互いに影響します.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">\"バックグラウンドでのポップアップ表示\" (\"バックグラウンドでの画面起動\" / \"バックグラウンドでの画面表示\" とも呼ばれます) 権限により/アプリがバックグラウンドにある/または画面が表示されていない場合でも/AutoJs6 が Activity を起動したり特定の設定ページを開いたりできます.[ 定時タスク発火時に UI を開く / ロック画面や待機後に対話を復帰する / 通知やショートカットからスクリプト設定ページを開く ] といったシーンに適しています.\n\nNote: Xiaomi (MIUI/HyperOS)/Vivo (OriginOS/Funtouch OS) などでは/この権限が既定でオフになっている場合があります. 未付与の場合/バックグラウンドからページを開こうとするスクリプトがシステムにブロックされ/[ 無反応 / バックグラウンドでのみ実行され画面が出ない / 遷移失敗 ] のように見えることがあります.\n\n 付与後でも/[ バッテリー最適化 / 自動起動制限 / バックグラウンド凍結 / アプリスタンバイ ] など他のシステムポリシーの影響を受ける可能性があります. 関連する権限やホワイトリスト設定と併せて調整してください.</string>
<string name="description_change_working_dir_preference">スクリプトを含むディレクトリのパスを変更する</string>
<string name="description_check_for_updates_preference">AutoJs6 は, GitHub からアップデートを取得し, ダウンロードします.</string>
<string name="description_client_mode">クライアントモードは, AutoJs6 がリモートのサーバーへ能動的に接続し, [ スクリプト転送 / ログ出力 / リモート制御 ] などを行うための機能です.\n\n通常は, 端末とサーバーが同一 LAN か, 相互に到達可能なネットワーク環境にある必要があります.</string>
@@ -110,7 +111,9 @@
<string name="description_version_histories_preference">GitHub 上の AutoJs6 のリリース版変更履歴と主要カテゴリの統計データを確認する.</string>
<string name="description_write_secure_settings">アプリケーションが読み取ることはできるが, 書き込むことはできないシステム環境設定を含む, 安全なシステム設定です.\nこれは, ユーザーがシステムアプリの UI を通じて明示的に変更する必要がある環境設定のためのものです.\nセキュアなシステム設定を許可すると, 通常のアプリケーションはセキュアな設定 (アクセシビリティサービスなど) を直接変更できるようになります</string>
<string name="description_write_system_settings">\"システム設定の変更\" 権限により, AutoJs6 は一部のシステム設定を変更でき, スクリプトで [ 画面の明るさ / 自動回転 / 画面タイムアウト ] などを調整できます.</string>
<string name="dialog_button_abandon">放棄</string>
<string name="dialog_button_advanced_settings">詳細</string>
<string name="dialog_button_amend_host_address">アドレスを修正</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">取消</string>
<string name="dialog_button_cancel_download">取消</string>
@@ -118,7 +121,6 @@
<string name="dialog_button_confirm">OK</string>
<string name="dialog_button_connect">接続</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">@string/dialog_button_continue</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">デフォ前置</string>
<string name="dialog_button_details">詳細</string>
@@ -128,12 +130,12 @@
<string name="dialog_button_file_information">ファイル情報</string>
<string name="dialog_button_history">履歴</string>
<string name="dialog_button_ignore_current_update">無視する</string>
<string name="dialog_button_interrupt_connection">接続を中止</string>
<string name="dialog_button_join_group">グループ参加</string>
<string name="dialog_button_manager">マネージャー</string>
<string name="dialog_button_more">詳細</string>
<string name="dialog_button_open_color_palette">パレットを開く</string>
<string name="dialog_button_quit">終了する</string>
<string name="dialog_button_quit_connecting">@string/dialog_button_quit</string>
<string name="dialog_button_remove">削除</string>
<string name="dialog_button_retrieve">取得</string>
<string name="dialog_button_retry">再試行</string>
@@ -213,6 +215,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">有効な IP アドレスの後にコロンが続くこと</string>
<string name="error_compass_cannot_be_null">コンパスは null にできません</string>
<string name="error_connect_to_remote">リモートサーバーに接続できません. %s</string>
<string name="error_connection_failed_with_host">\"%1$s\" への接続に失敗しました.</string>
<string name="error_connection_timed_out">接続がタイムアウトしました (%d ミリ秒)</string>
<string name="error_continuation_resume_called_without_suspend">resume() メソッドは suspend() の後に呼び出す必要があります</string>
<string name="error_continuation_suspend_called_more_than_once">suspend() メソッドは一度だけコールされるべきです</string>
<string name="error_corresponding_github_release_may_not_published">対応する GitHub のリリースがまだ公開されていない可能性があります</string>
@@ -266,6 +270,7 @@
<string name="error_invalid_rest_compass">無効な残りコンパスです: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">無効なセレクタ・マップ要素です. { %s: %s (%s) } です</string>
<string name="error_ip_address_should_not_be_empty">IP アドレスは空であってはならない</string>
<string name="error_ipv6_port_requires_brackets">IPv6 のポートは \"[ipv6]:port\" 形式で指定する必要があります.</string>
<string name="error_max_listeners_exceeded">リスナーの最大数を超えています: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 は \"auto\" ファイルを実行するためのルート・アクセス権を持っていない可能性があります</string>
<string name="error_method_called_with_null_argument" formatted="false">null 引数で %s() が呼び出されました: %s</string>
@@ -338,6 +343,7 @@
<string name="hint_long_click_run_to_debug">実行」ボタン長押しでデバッグ</string>
<string name="hint_loop_delay">ループ前の遅延時間</string>
<string name="hint_loop_times">無限ループの場合は 0</string>
<string name="hint_pc_server_address_supported_formats">IPv4/IPv6/およびドメイン名に対応しています.</string>
<string name="instruction_install_plugin_from_url">リモートプラグインの URL を入力してください. \n例: \"https://example.com/plugin.apk\"</string>
<string name="label_latest_used_time">最終使用時: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">\"Blob\" スレッドが失敗</string>
@@ -562,7 +568,10 @@
<string name="text_connect_to_pc">PC に接続する</string>
<string name="text_connected">接続済み</string>
<string name="text_connected_count_with_colon">接続済み: %1$d</string>
<string name="text_connecting">接続中</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">\"%1$s\" に接続しています...</string>
<string name="text_connection_cannot_be_established">接続が確立できない</string>
<string name="text_connection_failed">接続に失敗しました</string>
<string name="text_console">コンソール</string>
<string name="text_continue">続行</string>
<string name="text_copy">コピー</string>
@@ -1155,6 +1164,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">バージョン名 (計算中...)</string>
<string name="text_view">表示</string>
<string name="text_view_docs">ドキュメントを見る</string>
<string name="text_vivo_background_popup_permission">バックグラウンドでのポップアップ表示</string>
<string name="text_vsc_ext_version_not_meet_requirement">VSCode 拡張機能のバージョンが要件を満たしていない</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">すべてのデータ処理が完了するのを待機しています...</string>
<string name="text_weekly_task">週次タスク</string>
@@ -1162,5 +1172,6 @@
<string name="text_working_dir_path">作業ディレクトリのパス</string>
<string name="text_write_secure_settings">セキュリティ設定の書き込み</string>
<string name="text_write_system_settings">システム設定の書き込み</string>
<string name="text_xiaomi_background_popup_permission">バックグラウンドでのポップアップ表示</string>
</resources>

View File

@@ -74,6 +74,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">\"모든 파일 관리\" (또는 \"모든 파일 접근\") 권한은 AutoJs6 가 공유 저장소에서 일반적인 파일 경로를 통해 직접 [ 생성 / 읽기 / 수정 / 삭제 ] 를 수행할 수 있게 하여, 스크립트가 \"Internal Storage\"에 접근할 수 있도록 하고 파일 관리자가 파일을 정상적으로 표시 및 관리할 수 있도록 합니다.\n\nAndroid 11+ 기기에서는 전체 저장소 파일 읽기/쓰기 기능을 구현하는 주요 방식입니다.</string>
<string name="description_app_language_preference">실행 스크립트의 예외 메시지를 포함하여 AutoJs6 의 디스플레이 언어를 변경하는이 선호도.\n\n참고: 예상대로 언어를 적용하려면 앱 재시작이 필요할 수 있습니다.</string>
<string name="description_auto_night_mode">자동 야간 모드를 켜면 AutoJs6 가 시스템 설정에 따라 자동으로 야간 모드를 전환합니다.\n\nNote: 자동 야간 모드 스위치와 야간 모드 스위치는 서로 연동되며 상호 영향을 줍니다.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">\"백그라운드 팝업\" (또는 \"백그라운드에서 화면 시작\" / \"백그라운드에서 화면 표시\") 권한은 앱이 백그라운드에 있거나 화면이 표시되지 않는 상태에서도 AutoJs6 가 Activity 를 시작하거나 특정 설정 페이지를 열 수 있도록 합니다. [ 예약 작업 트리거 시 UI 열기 / 잠금 화면 또는 대기 후 상호작용 복원 / 알림 또는 바로가기에서 스크립트 설정 페이지 호출 ] 같은 시나리오에 적합합니다.\n\nNote: Xiaomi (MIUI/HyperOS), Vivo (OriginOS/Funtouch OS) 등의 시스템에서는 이 권한이 기본적으로 꺼져 있을 수 있습니다. 권한이 없으면 스크립트가 백그라운드에서 페이지를 열려고 할 때 시스템이 차단하여 [ 무반응 / 백그라운드에서만 실행되고 화면이 표시되지 않음 / 이동 실패 ] 로 보일 수 있습니다.\n\n 권한을 부여한 후에도 [ 배터리 최적화 / 자동 시작 제한 / 백그라운드 동결 / 앱 대기 ] 등 다른 시스템 정책의 영향을 받을 수 있으므로, 관련 권한이나 화이트리스트 설정과 함께 조정하는 것을 권장합니다.</string>
<string name="description_change_working_dir_preference">스크립트가 포함 된 디렉토리의 경로를 변경하십시오</string>
<string name="description_check_for_updates_preference">AutoJs6 은 GitHub 에서 업데이트를 가져오고 다운로드합니다.</string>
<string name="description_client_mode">클라이언트 모드는 AutoJs6 가 원격 서버에 능동적으로 연결하여 [ 스크립트 전송 / 로그 출력 / 원격 제어 ] 등을 수행할 수 있도록 합니다.\n\n 일반적으로 기기와 서버는 동일한 LAN 또는 서로 접근 가능한 네트워크 환경에 있어야 합니다.</string>
@@ -111,7 +112,9 @@
<string name="description_version_histories_preference">GitHub 에서 AutoJs6 릴리스 버전의 변경 기록과 주요 분류 통계 데이터를 확인하기.</string>
<string name="description_write_secure_settings">애플리케이션이 읽을 수 있지만 쓸 수없는 시스템 환경 설정을 포함하는 보안 시스템 설정.\n이들은 사용자가 시스템 앱의 UI 를 통해 명시 적으로 수정 해야하는 선호도입니다.\n보안 시스템 설정 권한을 사용하면 일반 애플리케이션이 보안 설정 (예: 접근성 서비스)을 직접 수정할 수 있습니다.</string>
<string name="description_write_system_settings">\"시스템 설정 수정\" 권한은 AutoJs6 가 일부 시스템 설정을 변경할 수 있게 하여, 스크립트가 [ 화면 밝기 / 자동 회전 / 화면 시간 제한 ] 등의 설정을 변경할 수 있도록 합니다.</string>
<string name="dialog_button_abandon">포기</string>
<string name="dialog_button_advanced_settings">고급</string>
<string name="dialog_button_amend_host_address">주소 수정</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">취소</string>
<string name="dialog_button_cancel_download">취소</string>
@@ -119,7 +122,6 @@
<string name="dialog_button_confirm">확인</string>
<string name="dialog_button_connect">연결</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">@string/dialog_button_continue</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">기본 접두사</string>
<string name="dialog_button_details">세부</string>
@@ -129,12 +131,12 @@
<string name="dialog_button_file_information">파일 정보</string>
<string name="dialog_button_history">역사</string>
<string name="dialog_button_ignore_current_update">무시하다</string>
<string name="dialog_button_interrupt_connection">연결 중단</string>
<string name="dialog_button_join_group">그룹 가입</string>
<string name="dialog_button_manager">관리자</string>
<string name="dialog_button_more"></string>
<string name="dialog_button_open_color_palette">팔레트 열기</string>
<string name="dialog_button_quit">그만두다</string>
<string name="dialog_button_quit_connecting">@string/dialog_button_quit</string>
<string name="dialog_button_remove">제거하다</string>
<string name="dialog_button_retrieve">가져오기</string>
<string name="dialog_button_retry">다시 해 보다</string>
@@ -214,6 +216,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">콜론은 유효한 IP 주소를 따라야 합니다</string>
<string name="error_compass_cannot_be_null">나침반은 무효가 될 수 없다</string>
<string name="error_connect_to_remote">원격 서버에 연결할 수 없습니다: %s</string>
<string name="error_connection_failed_with_host">\"%1$s\"에 연결하지 못했습니다.</string>
<string name="error_connection_timed_out">연결 시간 초과 (%d 밀리초)</string>
<string name="error_continuation_resume_called_without_suspend">메소드 resume() 가 suspend() 후 호출해야합니다.</string>
<string name="error_continuation_suspend_called_more_than_once">메소드 suspend() 는 한 번만 호출해야합니다</string>
<string name="error_corresponding_github_release_may_not_published">해당 GitHub 릴리스는 아직 게시되지 않았을 수 있습니다</string>
@@ -267,6 +271,7 @@
<string name="error_invalid_rest_compass">잘못된 휴식 나침반: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">잘못된 선택기 맵 요소: { %s: %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">IP 주소는 비워둘 수 없습니다</string>
<string name="error_ipv6_port_requires_brackets">IPv6 포트는 \"[ipv6]:port\" 형식으로 지정해야 합니다.</string>
<string name="error_max_listeners_exceeded">최대 청취자를 초과했습니다: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 은 \"auto\" 파일을 실행할 루트 액세스 권한이 없을 수 있습니다.</string>
<string name="error_method_called_with_null_argument" formatted="false">널 인수로 호출 된 %s(): %s</string>
@@ -339,6 +344,7 @@
<string name="hint_long_click_run_to_debug">\"실행\" 버튼을 긴 클릭하여 디버그를 클릭하십시오</string>
<string name="hint_loop_delay">루프 전 지연</string>
<string name="hint_loop_times">무한 루프의 경우 0</string>
<string name="hint_pc_server_address_supported_formats">IPv4, IPv6 및 도메인을 지원합니다.</string>
<string name="instruction_install_plugin_from_url">원격 플러그인 주소를 가리키는 URL을 입력하세요.\n예: \"https://example.com/plugin.apk\"</string>
<string name="label_latest_used_time">최종 사용: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">\"Blob\" 스레드 요청 실패</string>
@@ -563,7 +569,10 @@
<string name="text_connect_to_pc">PC 에 연결하십시오</string>
<string name="text_connected">연결됨</string>
<string name="text_connected_count_with_colon">연결됨: %1$d</string>
<string name="text_connecting">연결 중</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">\"%1$s\"에 연결하는 중...</string>
<string name="text_connection_cannot_be_established">연결을 설정할 수 없습니다</string>
<string name="text_connection_failed">연결 실패</string>
<string name="text_console">콘솔</string>
<string name="text_continue">계속하다</string>
<string name="text_copy">복사</string>
@@ -1156,6 +1165,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">버전 이름 (계산 중...)</string>
<string name="text_view">보기</string>
<string name="text_view_docs">문서를 봅니다</string>
<string name="text_vivo_background_popup_permission">백그라운드 팝업</string>
<string name="text_vsc_ext_version_not_meet_requirement">VSCode 확장 버전이 요구 사항을 충족하지 않습니다</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">모든 데이터 처리가 완료될 때까지 대기 중입니다...</string>
<string name="text_weekly_task">주간 과제</string>
@@ -1163,5 +1173,6 @@
<string name="text_working_dir_path">작업 디렉토리 경로</string>
<string name="text_write_secure_settings">보안 설정을 작성하십시오</string>
<string name="text_write_system_settings">시스템 설정을 작성하십시오</string>
<string name="text_xiaomi_background_popup_permission">백그라운드 팝업</string>
</resources>

View File

@@ -72,6 +72,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">Разрешение \"управление всеми файлами\" (или \"доступ ко всем файлам\") позволяет AutoJs6 напрямую [ создавать / читать / изменять / удалять ] файлы в общем хранилище через обычные пути файлов, чтобы скрипты могли получать доступ к \"Internal Storage\", а файловый менеджер мог корректно отображать и управлять файлами.\n\nНа устройствах Android 11+ это основной способ обеспечить полный доступ к чтению/записи файлов.</string>
<string name="description_app_language_preference">Это предпочтение для изменения языка отображения AutoJs6, включая сообщения исключений от запущенных скриптов.\n\nПримечание: может потребоваться перезапуск приложения, чтобы язык был применен так, как ожидается.</string>
<string name="description_auto_night_mode">После включения автоматического ночного режима AutoJs6 будет автоматически переключать ночной режим в соответствии с настройками системы.\n\nNote: переключатель автоматического ночного режима и переключатель ночного режима связаны и взаимно влияют друг на друга.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">Разрешение \"всплывающие окна в фоне\" (также известное как \"запуск интерфейса из фона\" или \"показ интерфейса в фоне\") позволяет AutoJs6, даже когда приложение находится в фоне или без видимого интерфейса, запускать Activity или открывать определённую страницу настроек. Подходит для сценариев [ открывать UI при срабатывании запланированной задачи / восстанавливать взаимодействие после блокировки экрана или режима ожидания / открывать страницу настройки скрипта из уведомления или ярлыка ].\n\nNote: на системах Xiaomi (MIUI/HyperOS), Vivo (OriginOS/Funtouch OS) и т. п. это разрешение может быть отключено по умолчанию. Если оно не выдано, система может блокировать попытки скрипта открыть страницу из фона, что может проявляться как [ нет реакции / выполнение только в фоне без интерфейса / неудачный переход ].\n\nДаже после выдачи разрешения на поведение могут влиять другие политики системы, такие как [ оптимизация батареи / ограничения автозапуска / заморозка в фоне / режим ожидания приложения ]. Рекомендуется настраивать совместно с связанными разрешениями или белыми списками.</string>
<string name="description_change_working_dir_preference">Измените путь к директории, содержащей скрипты</string>
<string name="description_check_for_updates_preference">AutoJs6 получает и загружает обновления с GitHub.</string>
<string name="description_client_mode">Клиентский режим позволяет AutoJs6 активно подключаться к удалённому серверу для выполнения [ передачи скриптов / вывода логов / удалённого управления ].\n\nОбычно устройство и сервер должны находиться в одной локальной сети (LAN) или в сетевой среде, где они могут обращаться друг к другу.</string>
@@ -109,7 +110,9 @@
<string name="description_version_histories_preference">Просмотреть историю выпусков AutoJs6 на GitHub и статистику по основным категориям.</string>
<string name="description_write_secure_settings">Настройки безопасности системы, содержащие системные предпочтения, которые приложения могут читать, но не имеют права записывать.\nОни предназначены для параметров, которые пользователь должен явно изменить через пользовательский интерфейс системного приложения.\nПри наличии разрешения на безопасные системные настройки обычные приложения могут напрямую изменять безопасные настройки (например, служба доступности).</string>
<string name="description_write_system_settings">Разрешение \"изменение системных настроек\" позволяет AutoJs6 изменять некоторые параметры системы, чтобы скрипты могли менять такие настройки, как [ яркость экрана / автоповорот / тайм-аут экрана ].</string>
<string name="dialog_button_abandon">Отказаться</string>
<string name="dialog_button_advanced_settings">Доп.</string>
<string name="dialog_button_amend_host_address">Исправить адрес</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">Отменить</string>
<string name="dialog_button_cancel_download">Отменить</string>
@@ -117,7 +120,6 @@
<string name="dialog_button_confirm">OK</string>
<string name="dialog_button_connect">Подключиться</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">@string/dialog_button_continue</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">Префикс деф.</string>
<string name="dialog_button_details">Подробности</string>
@@ -127,12 +129,12 @@
<string name="dialog_button_file_information">Информация о файле</string>
<string name="dialog_button_history">История</string>
<string name="dialog_button_ignore_current_update">Игнорировать</string>
<string name="dialog_button_interrupt_connection">Прервать соединение</string>
<string name="dialog_button_join_group">Вступить группу</string>
<string name="dialog_button_manager">Менеджер</string>
<string name="dialog_button_more">Еще</string>
<string name="dialog_button_open_color_palette">Открыть палитру</string>
<string name="dialog_button_quit">Выйти</string>
<string name="dialog_button_quit_connecting">@string/dialog_button_quit</string>
<string name="dialog_button_remove">Удалить</string>
<string name="dialog_button_retrieve">Получить</string>
<string name="dialog_button_retry">Повторная попытка</string>
@@ -212,6 +214,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">Двоеточие должно следовать за действительным IP-адресом</string>
<string name="error_compass_cannot_be_null">Компас не может быть нулевым</string>
<string name="error_connect_to_remote">Не удается подключиться к удаленному серверу: %s</string>
<string name="error_connection_failed_with_host">Не удалось подключиться к \"%1$s\".</string>
<string name="error_connection_timed_out">Тайм-аут подключения (%d мс)</string>
<string name="error_continuation_resume_called_without_suspend">Метод resume() должен вызываться после suspend()</string>
<string name="error_continuation_suspend_called_more_than_once">Метод suspend() должен быть вызван только один раз</string>
<string name="error_corresponding_github_release_may_not_published">Соответствующий релиз на GitHub может быть еще не опубликован</string>
@@ -265,6 +269,7 @@
<string name="error_invalid_rest_compass">Неверный остаток компаса: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">Неверный элемент карты селектора: { %s: %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">IP-адрес не может быть пустым</string>
<string name="error_ipv6_port_requires_brackets">Порт IPv6 должен быть указан в формате \"[ipv6]:port\".</string>
<string name="error_max_listeners_exceeded">Превышено максимальное количество слушателей: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 может не иметь root-доступа для запуска файла \"auto\"</string>
<string name="error_method_called_with_null_argument" formatted="false">%s() вызвана с нулевым аргументом: %s</string>
@@ -337,6 +342,7 @@
<string name="hint_long_click_run_to_debug">Длительное нажатие кнопки \"Выполнить\" для отладки</string>
<string name="hint_loop_delay">Задержка перед циклом</string>
<string name="hint_loop_times">0 для бесконечного цикла</string>
<string name="hint_pc_server_address_supported_formats">Поддерживаются IPv4, IPv6 и доменные имена.</string>
<string name="instruction_install_plugin_from_url">Введите URL, указывающий на удалённый плагин.\nНапример: \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">Последнее использование: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">Поток \"blob\" неудачен</string>
@@ -561,7 +567,10 @@
<string name="text_connect_to_pc">Подключитесь к ПК</string>
<string name="text_connected">Подключено</string>
<string name="text_connected_count_with_colon">Подключено: %1$d</string>
<string name="text_connecting">Подключение...</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">Подключение к \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">Соединение не может быть установлено</string>
<string name="text_connection_failed">Не удалось подключиться</string>
<string name="text_console">Консоль</string>
<string name="text_continue">Продолжить</string>
<string name="text_copy">Скопировать</string>
@@ -1154,6 +1163,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Название версии (Вычисление...)</string>
<string name="text_view">Просмотр</string>
<string name="text_view_docs">Просмотр документов</string>
<string name="text_vivo_background_popup_permission">Всплывающие окна в фоне</string>
<string name="text_vsc_ext_version_not_meet_requirement">Версия расширения VSCode не соответствует требованиям</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">Ожидание завершения обработки всех данных...</string>
<string name="text_weekly_task">Недельное задание</string>
@@ -1161,5 +1171,6 @@
<string name="text_working_dir_path">Путь к рабочему каталогу</string>
<string name="text_write_secure_settings">Параметры безопасности записи</string>
<string name="text_write_system_settings">Запись системных настроек</string>
<string name="text_xiaomi_background_popup_permission">Всплывающие окна в фоне</string>
</resources>

View File

@@ -71,6 +71,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">\"管理所有文件\" (或 \"所有文件訪問\") 權限允許 AutoJs6 在共享存儲空間中通過常規文件路徑直接 [ 創建 / 讀取 / 修改 / 刪除 ] 文件, 使腳本可以訪問 \"內部存儲 (Internal Storage)\", 使文件管理器可以正常顯示及管理文件.\n\n在 Android 11+ 設備上, 這是實現全盤文件讀寫能力的主要方式.</string>
<string name="description_app_language_preference">設置選項用於修改 AutoJs6 應用的文本內容顯示語言, 同時包括腳本運行產生的錯誤消息等.\n\n注: 部分內容可能需要重啓應用才能完成語言切換.</string>
<string name="description_auto_night_mode">自動夜間模式開啓後, AutoJs6 將根據系統設置自動切換夜間模式.\n\注: 自動夜間模式開關與夜間模式開關互相關聯且互相影響.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">\"後台彈出界面\" (也稱 \"後台啓動界面\" 或 \"後台彈出頁面\") 權限允許 AutoJs6 在應用位於後台或未顯示界面時, 仍可主動啓動界面(Activity) 或打開特定設置頁, 適合 [ 定時任務觸發時打開 UI / 在鎖屏或待機後恢復交互 / 由通知或快捷方式喚起腳本配置頁 ] 等場景.\n\n注: 在 Xiaomi (MIUI/HyperOS), Vivo (OriginOS/Funtouch OS) 等系統上, 該權限可能默認關閉. 未授予時, 腳本嘗試從後台打開頁面可能會被系統攔截, 表現為 [ 無響應 / 僅後台執行但不顯示界面 / 跳轉失敗 ].\n\n授予後仍可能受到系統其他策略影響, 如 [ 電池優化 / 自啓動限制 / 後台凍結 / 應用待機 ] 等, 可結合相關權限或白名單設置一起調整.</string>
<string name="description_change_working_dir_preference">更改包含腳本的文件夾路徑</string>
<string name="description_check_for_updates_preference">AutoJs6 從 GitHub 獲取並下載更新.</string>
<string name="description_client_mode">客户端模式用於讓 AutoJs6 主動連接到遠端服務端, 以便進行 [ 腳本傳輸 / 打印日誌 / 遠程控制 ] 等.\n\n通常需要設備與服務端處於同一局域網或可互相訪問的網絡環境.</string>
@@ -107,7 +108,9 @@
<string name="description_version_histories_preference">查看 AutoJs6 在 GitHub 發行版本的歷史更新記錄及重要分類的統計數據.</string>
<string name="description_write_secure_settings">安全設置包含應用程序可讀但不可寫入的設置選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設置權限\" 後, 普通應用可直接修改上述安全設置 (例如無障礙服務).</string>
<string name="description_write_system_settings">\"修改系統設置\" 權限允許 AutoJs6 修改部分系統設置項, 使腳本可以修改 [ 屏幕亮度 / 自動旋轉 / 屏幕超時 ] 等系統設置參數.</string>
<string name="dialog_button_abandon">放棄</string>
<string name="dialog_button_advanced_settings">高級設置</string>
<string name="dialog_button_amend_host_address">修正地址</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">取消</string>
<string name="dialog_button_cancel_download">取消下載</string>
@@ -115,7 +118,6 @@
<string name="dialog_button_confirm">確定</string>
<string name="dialog_button_connect">連接</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">繼續連接</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">默認前綴</string>
<string name="dialog_button_details">瞭解更多</string>
@@ -125,12 +127,12 @@
<string name="dialog_button_file_information">文件信息</string>
<string name="dialog_button_history">歷史記錄</string>
<string name="dialog_button_ignore_current_update">忽略此版本</string>
<string name="dialog_button_interrupt_connection">中止連接</string>
<string name="dialog_button_join_group">加入羣組</string>
<string name="dialog_button_manager">管理器</string>
<string name="dialog_button_more">瞭解更多</string>
<string name="dialog_button_open_color_palette">打開調色盤</string>
<string name="dialog_button_quit">放棄</string>
<string name="dialog_button_quit_connecting">放棄連接</string>
<string name="dialog_button_remove">移除</string>
<string name="dialog_button_retrieve">獲取</string>
<string name="dialog_button_retry">重試</string>
@@ -210,6 +212,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">冒號需跟隨有效的 IP 地址</string>
<string name="error_compass_cannot_be_null">羅盤參數不能為 null</string>
<string name="error_connect_to_remote">連接失敗: %s</string>
<string name="error_connection_failed_with_host">連接至 \"%1$s\" 失敗.</string>
<string name="error_connection_timed_out">連接超時 (%d 毫秒)</string>
<string name="error_continuation_resume_called_without_suspend">方法 resume() 調用前需先調用 suspend()</string>
<string name="error_continuation_suspend_called_more_than_once">方法 suspend() 只能調用一次</string>
<string name="error_corresponding_github_release_may_not_published">對應的 GitHub 發行版可能還未發佈</string>
@@ -263,6 +267,7 @@
<string name="error_invalid_rest_compass">無效的剩餘羅盤參數: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">不合法的選擇器集合元素: { %s: %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">IP 地址不可為空</string>
<string name="error_ipv6_port_requires_brackets">IPv6 端口必須使用 \"[ipv6]:port\" 格式.</string>
<string name="error_max_listeners_exceeded">超出最大監聽器數量限制: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 可能因缺少 Root 權限而無法運行 \"auto\" 文件</string>
<string name="error_method_called_with_null_argument" formatted="false">%s() 傳入的 %s 參數為空</string>
@@ -335,6 +340,7 @@
<string name="hint_long_click_run_to_debug">長按 \"運行\" 圖標可啓動調試</string>
<string name="hint_loop_delay">開始循環前的延遲</string>
<string name="hint_loop_times">0 表示無限循環</string>
<string name="hint_pc_server_address_supported_formats">支持 IPv4, IPv6 及域名.</string>
<string name="instruction_install_plugin_from_url">輸入一個指向遠程插件地址的 URL.\n例如 \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">最近使用: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">線程 "blob" 請求失敗</string>
@@ -559,7 +565,10 @@
<string name="text_connect_to_pc">連接到計算機</string>
<string name="text_connected">已連接</string>
<string name="text_connected_count_with_colon">已連接: %1$d</string>
<string name="text_connecting">連接中</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">正在連接至 \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">無法建立連接</string>
<string name="text_connection_failed">連接失敗</string>
<string name="text_console">控制枱</string>
<string name="text_continue">繼續</string>
<string name="text_copy">複製</string>
@@ -1152,6 +1161,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">版本名稱 (計算中...)</string>
<string name="text_view">查看</string>
<string name="text_view_docs">查看文檔</string>
<string name="text_vivo_background_popup_permission">後台彈出界面</string>
<string name="text_vsc_ext_version_not_meet_requirement">VSCode 插件版本不符合要求</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">正在等待全部數據處理完畢...</string>
<string name="text_weekly_task">每週任務</string>
@@ -1159,5 +1169,6 @@
<string name="text_working_dir_path">工作路徑</string>
<string name="text_write_secure_settings">修改安全設置</string>
<string name="text_write_system_settings">修改系統設置</string>
<string name="text_xiaomi_background_popup_permission">後台彈出界面</string>
</resources>

View File

@@ -71,6 +71,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">\"管理所有檔案\" (或 \"所有檔案訪問\") 許可權允許 AutoJs6 在共享儲存空間中透過常規檔案路徑直接 [ 建立 / 讀取 / 修改 / 刪除 ] 檔案, 使指令碼可以訪問 \"內部儲存 (Internal Storage)\", 使檔案管理器可以正常顯示及管理檔案.\n\n在 Android 11+ 裝置上, 這是實現全盤檔案讀寫能力的主要方式.</string>
<string name="description_app_language_preference">設定選項用於修改 AutoJs6 應用的文字內容顯示語言, 同時包括指令碼執行產生的錯誤訊息等.\n\n注: 部分內容可能需要重啟應用才能完成語言切換.</string>
<string name="description_auto_night_mode">自動夜間模式開啟後, AutoJs6 將根據系統設定自動切換夜間模式.\n\注: 自動夜間模式開關與夜間模式開關互相關聯且互相影響.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">\"後臺彈出介面\" (也稱 \"後臺啟動介面\" 或 \"後臺彈出頁面\") 許可權允許 AutoJs6 在應用位於後臺或未顯示介面時, 仍可主動啟動介面(Activity) 或開啟特定設定頁, 適合 [ 定時任務觸發時開啟 UI / 在鎖屏或待機後恢復互動 / 由通知或快捷方式喚起指令碼配置頁 ] 等場景.\n\n注: 在 Xiaomi (MIUI/HyperOS), Vivo (OriginOS/Funtouch OS) 等系統上, 該許可權可能預設關閉. 未授予時, 指令碼嘗試從後臺開啟頁面可能會被系統攔截, 表現為 [ 無響應 / 僅後臺執行但不顯示介面 / 跳轉失敗 ].\n\n授予後仍可能受到系統其他策略影響, 如 [ 電池最佳化 / 自啟動限制 / 後臺凍結 / 應用待機 ] 等, 可結合相關許可權或白名單設定一起調整.</string>
<string name="description_change_working_dir_preference">更改包含指令碼的資料夾路徑</string>
<string name="description_check_for_updates_preference">AutoJs6 從 GitHub 獲取並下載更新.</string>
<string name="description_client_mode">客戶端模式用於讓 AutoJs6 主動連線到遠端服務端, 以便進行 [ 指令碼傳輸 / 列印日誌 / 遠端控制 ] 等.\n\n通常需要裝置與服務端處於同一區域網或可互相訪問的網路環境.</string>
@@ -107,7 +108,9 @@
<string name="description_version_histories_preference">檢視 AutoJs6 在 GitHub 發行版本的歷史更新記錄及重要分類的統計資料.</string>
<string name="description_write_secure_settings">安全設定包含應用程式可讀但不可寫入的設定選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設定許可權\" 後, 普通應用可直接修改上述安全設定 (例如無障礙服務).</string>
<string name="description_write_system_settings">\"修改系統設定\" 許可權允許 AutoJs6 修改部分系統設定項, 使指令碼可以修改 [ 螢幕亮度 / 自動旋轉 / 螢幕超時 ] 等系統設定引數.</string>
<string name="dialog_button_abandon">放棄</string>
<string name="dialog_button_advanced_settings">高階設定</string>
<string name="dialog_button_amend_host_address">修正地址</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">取消</string>
<string name="dialog_button_cancel_download">取消下載</string>
@@ -115,7 +118,6 @@
<string name="dialog_button_confirm">確定</string>
<string name="dialog_button_connect">連線</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">繼續連線</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">預設字首</string>
<string name="dialog_button_details">瞭解更多</string>
@@ -125,12 +127,12 @@
<string name="dialog_button_file_information">檔案資訊</string>
<string name="dialog_button_history">歷史記錄</string>
<string name="dialog_button_ignore_current_update">忽略此版本</string>
<string name="dialog_button_interrupt_connection">中止連線</string>
<string name="dialog_button_join_group">加入群組</string>
<string name="dialog_button_manager">管理器</string>
<string name="dialog_button_more">瞭解更多</string>
<string name="dialog_button_open_color_palette">開啟調色盤</string>
<string name="dialog_button_quit">放棄</string>
<string name="dialog_button_quit_connecting">放棄連線</string>
<string name="dialog_button_remove">移除</string>
<string name="dialog_button_retrieve">獲取</string>
<string name="dialog_button_retry">重試</string>
@@ -210,6 +212,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">冒號需跟隨有效的 IP 地址</string>
<string name="error_compass_cannot_be_null">羅盤引數不能為 null</string>
<string name="error_connect_to_remote">連線失敗: %s</string>
<string name="error_connection_failed_with_host">連線至 \"%1$s\" 失敗.</string>
<string name="error_connection_timed_out">連線超時 (%d 毫秒)</string>
<string name="error_continuation_resume_called_without_suspend">方法 resume() 呼叫前需先呼叫 suspend()</string>
<string name="error_continuation_suspend_called_more_than_once">方法 suspend() 只能呼叫一次</string>
<string name="error_corresponding_github_release_may_not_published">對應的 GitHub 發行版可能還未釋出</string>
@@ -263,6 +267,7 @@
<string name="error_invalid_rest_compass">無效的剩餘羅盤引數: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">不合法的選擇器集合元素: { %s: %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">IP 地址不可為空</string>
<string name="error_ipv6_port_requires_brackets">IPv6 埠必須使用 \"[ipv6]:port\" 格式.</string>
<string name="error_max_listeners_exceeded">超出最大監聽器數量限制: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 可能因缺少 Root 許可權而無法執行 \"auto\" 檔案</string>
<string name="error_method_called_with_null_argument" formatted="false">%s() 傳入的 %s 引數為空</string>
@@ -335,6 +340,7 @@
<string name="hint_long_click_run_to_debug">長按 \"執行\" 圖示可啟動除錯</string>
<string name="hint_loop_delay">開始迴圈前的延遲</string>
<string name="hint_loop_times">0 表示無限迴圈</string>
<string name="hint_pc_server_address_supported_formats">支援 IPv4, IPv6 及域名.</string>
<string name="instruction_install_plugin_from_url">輸入一個指向遠端外掛地址的 URL.\n例如 \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">最近使用: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">執行緒 "blob" 請求失敗</string>
@@ -559,7 +565,10 @@
<string name="text_connect_to_pc">連線到計算機</string>
<string name="text_connected">已連線</string>
<string name="text_connected_count_with_colon">已連線: %1$d</string>
<string name="text_connecting">連線中</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">正在連線至 \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">無法建立連線</string>
<string name="text_connection_failed">連線失敗</string>
<string name="text_console">控制檯</string>
<string name="text_continue">繼續</string>
<string name="text_copy">複製</string>
@@ -1152,6 +1161,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">版本名稱 (計算中...)</string>
<string name="text_view">檢視</string>
<string name="text_view_docs">檢視文件</string>
<string name="text_vivo_background_popup_permission">後臺彈出介面</string>
<string name="text_vsc_ext_version_not_meet_requirement">VSCode 外掛版本不符合要求</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">正在等待全部資料處理完畢...</string>
<string name="text_weekly_task">每週任務</string>
@@ -1159,5 +1169,6 @@
<string name="text_working_dir_path">工作路徑</string>
<string name="text_write_secure_settings">修改安全設定</string>
<string name="text_write_system_settings">修改系統設定</string>
<string name="text_xiaomi_background_popup_permission">後臺彈出介面</string>
</resources>

View File

@@ -70,6 +70,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">\"管理所有文件\" (或 \"所有文件访问\") 权限允许 AutoJs6 在共享存储空间中通过常规文件路径直接 [ 创建 / 读取 / 修改 / 删除 ] 文件, 使脚本可以访问 \"内部存储 (Internal Storage)\", 使文件管理器可以正常显示及管理文件.\n\n在 Android 11+ 设备上, 这是实现全盘文件读写能力的主要方式.</string>
<string name="description_app_language_preference">设置选项用于修改 AutoJs6 应用的文本内容显示语言, 同时包括脚本运行产生的错误消息等.\n\n注: 部分内容可能需要重启应用才能完成语言切换.</string>
<string name="description_auto_night_mode">自动夜间模式开启后, AutoJs6 将根据系统设置自动切换夜间模式.\n\注: 自动夜间模式开关与夜间模式开关互相关联且互相影响.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">\"后台弹出界面\" (也称 \"后台启动界面\" 或 \"后台弹出页面\") 权限允许 AutoJs6 在应用位于后台或未显示界面时, 仍可主动启动界面(Activity) 或打开特定设置页, 适合 [ 定时任务触发时打开 UI / 在锁屏或待机后恢复交互 / 由通知或快捷方式唤起脚本配置页 ] 等场景.\n\n注: 在 Xiaomi (MIUI/HyperOS), Vivo (OriginOS/Funtouch OS) 等系统上, 该权限可能默认关闭. 未授予时, 脚本尝试从后台打开页面可能会被系统拦截, 表现为 [ 无响应 / 仅后台执行但不显示界面 / 跳转失败 ].\n\n授予后仍可能受到系统其他策略影响, 如 [ 电池优化 / 自启动限制 / 后台冻结 / 应用待机 ] 等, 可结合相关权限或白名单设置一起调整.</string>
<string name="description_change_working_dir_preference">更改包含脚本的文件夹路径</string>
<string name="description_check_for_updates_preference">AutoJs6 从 GitHub 获取并下载更新.</string>
<string name="description_client_mode">客户端模式用于让 AutoJs6 主动连接到远端服务端, 以便进行 [ 脚本传输 / 打印日志 / 远程控制 ] 等.\n\n通常需要设备与服务端处于同一局域网或可互相访问的网络环境.</string>
@@ -107,7 +108,9 @@
<string name="description_version_histories_preference">查看 AutoJs6 在 GitHub 发行版本的历史更新记录及重要分类的统计数据.</string>
<string name="description_write_secure_settings">安全设置包含应用程序可读但不可写入的设置选项, 这些选项只能由 UI 或系统级别应用修改.\n被授予 \"修改安全设置权限\" 后, 普通应用可直接修改上述安全设置 (例如无障碍服务).</string>
<string name="description_write_system_settings">\"修改系统设置\" 权限允许 AutoJs6 修改部分系统设置项, 使脚本可以修改 [ 屏幕亮度 / 自动旋转 / 屏幕超时 ] 等系统设置参数.</string>
<string name="dialog_button_abandon">放弃</string>
<string name="dialog_button_advanced_settings">高级设置</string>
<string name="dialog_button_amend_host_address">修正地址</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">取消</string>
<string name="dialog_button_cancel_download">取消下载</string>
@@ -115,7 +118,6 @@
<string name="dialog_button_confirm">确定</string>
<string name="dialog_button_connect">连接</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">继续连接</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">默认前缀</string>
<string name="dialog_button_details">了解更多</string>
@@ -125,12 +127,12 @@
<string name="dialog_button_file_information">文件信息</string>
<string name="dialog_button_history">历史记录</string>
<string name="dialog_button_ignore_current_update">忽略此版本</string>
<string name="dialog_button_interrupt_connection">中止连接</string>
<string name="dialog_button_join_group">加入群组</string>
<string name="dialog_button_manager">管理器</string>
<string name="dialog_button_more">了解更多</string>
<string name="dialog_button_open_color_palette">打开调色盘</string>
<string name="dialog_button_quit">放弃</string>
<string name="dialog_button_quit_connecting">放弃连接</string>
<string name="dialog_button_remove">移除</string>
<string name="dialog_button_retrieve">获取</string>
<string name="dialog_button_retry">重试</string>
@@ -210,6 +212,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">冒号需跟随有效的 IP 地址</string>
<string name="error_compass_cannot_be_null">罗盘参数不能为 null</string>
<string name="error_connect_to_remote">连接失败: %s</string>
<string name="error_connection_failed_with_host">连接至 \"%1$s\" 失败.</string>
<string name="error_connection_timed_out">连接超时 (%d 毫秒)</string>
<string name="error_continuation_resume_called_without_suspend">方法 resume() 调用前需先调用 suspend()</string>
<string name="error_continuation_suspend_called_more_than_once">方法 suspend() 只能调用一次</string>
<string name="error_corresponding_github_release_may_not_published">对应的 GitHub 发行版可能还未发布</string>
@@ -263,6 +267,7 @@
<string name="error_invalid_rest_compass">无效的剩余罗盘参数: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">不合法的选择器集合元素: { %s: %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">IP 地址不可为空</string>
<string name="error_ipv6_port_requires_brackets">IPv6 端口必须使用 \"[ipv6]:port\" 格式.</string>
<string name="error_max_listeners_exceeded">超出最大监听器数量限制: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 可能因缺少 Root 权限而无法运行 \"auto\" 文件</string>
<string name="error_method_called_with_null_argument" formatted="false">%s() 传入的 %s 参数为空</string>
@@ -335,6 +340,7 @@
<string name="hint_long_click_run_to_debug">长按 \"运行\" 图标可启动调试</string>
<string name="hint_loop_delay">开始循环前的延迟</string>
<string name="hint_loop_times">0 表示无限循环</string>
<string name="hint_pc_server_address_supported_formats">支持 IPv4, IPv6 及域名.</string>
<string name="instruction_install_plugin_from_url">输入一个指向远程插件地址的 URL.\n例如 \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">最近使用: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">线程 \"blob\" 请求失败</string>
@@ -559,7 +565,10 @@
<string name="text_connect_to_pc">连接到计算机</string>
<string name="text_connected">已连接</string>
<string name="text_connected_count_with_colon">已连接: %1$d</string>
<string name="text_connecting">连接中</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">正在连接至 \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">无法建立连接</string>
<string name="text_connection_failed">连接失败</string>
<string name="text_console">控制台</string>
<string name="text_continue">继续</string>
<string name="text_copy">复制</string>
@@ -1152,6 +1161,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">版本名称 (计算中...)</string>
<string name="text_view">查看</string>
<string name="text_view_docs">查看文档</string>
<string name="text_vivo_background_popup_permission">后台弹出界面</string>
<string name="text_vsc_ext_version_not_meet_requirement">VSCode 插件版本不符合要求</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">正在等待全部数据处理完毕...</string>
<string name="text_weekly_task">每周任务</string>
@@ -1159,5 +1169,6 @@
<string name="text_working_dir_path">工作路径</string>
<string name="text_write_secure_settings">修改安全设置</string>
<string name="text_write_system_settings">修改系统设置</string>
<string name="text_xiaomi_background_popup_permission">后台弹出界面</string>
</resources>

View File

@@ -324,6 +324,7 @@
<string name="description_all_files_access" tools:ignore="TypographyEllipsis">The \"manage all files\" (or \"all files access\") permission allows AutoJs6 to directly [ create / read / modify / delete ] files in shared storage via normal file paths, allowing scripts to access \"Internal Storage\" and enabling the file manager to display and manage files properly.\n\nOn Android 11+ devices, this is the primary way to achieve full file read/write access.</string>
<string name="description_app_language_preference">Preference for changing the display language of AutoJs6, including exception messages from running scripts.\n\nNote: an app restart may be needed to make language applied as expected.</string>
<string name="description_auto_night_mode">When auto night mode is enabled, AutoJs6 will automatically switch night mode based on system settings.\n\nNote: the auto night mode toggle and the night mode toggle are linked and affect each other.</string>
<string name="description_background_popup_permission" tools:ignore="TypographyEllipsis">The \"background pop-up\" permission (also known as \"background activity launch\" or \"launch UI from background\") allows AutoJs6 to proactively start an Activity or open specific settings pages even when the app is in the background or no UI is currently visible. It is useful for scenarios such as [ opening UI when a scheduled task is triggered / resuming interaction after the device is locked or idle / opening a script configuration page from a notification or shortcut ].\n\nNote: on systems like Xiaomi (MIUI/HyperOS) and Vivo (OriginOS/Funtouch OS), this permission may be disabled by default. Without it, attempts to open pages from the background may be blocked by the system, which can appear as [ no response / running in background only without showing UI / navigation failed ].\n\nEven after granting it, the behavior may still be affected by other system policies such as [ battery optimizations / auto-start restrictions / background freezing / app standby ]. Consider adjusting it together with related permissions or whitelist settings.</string>
<string name="description_change_working_dir_preference">Change the path of the directory containing scripts</string>
<string name="description_check_for_updates_preference">AutoJs6 gets and downloads updates from GitHub.</string>
<string name="description_client_mode">Client mode allows AutoJs6 to actively connect to a remote server for [ script transfer / log printing / remote control ].\n\nTypically, the device and the server need to be on the same LAN or on a network where they can reach each other.</string>
@@ -361,7 +362,9 @@
<string name="description_version_histories_preference">View the release version history and key category statistics of AutoJs6 on GitHub.</string>
<string name="description_write_secure_settings">Secure system settings, containing system preferences that applications can read but are not allowed to write.\nThese are for preferences that the user must explicitly modify through the UI of a system app.\nWith secure system settings permission, normal applications can directly modify the secure settings (such as accessibility service).</string>
<string name="description_write_system_settings">The \"write system settings\" permission allows AutoJs6 to modify some system settings, so scripts can change system parameters such as [ screen brightness / auto-rotate / screen timeout ].</string>
<string name="dialog_button_abandon">Abandon</string>
<string name="dialog_button_advanced_settings">Advanced</string>
<string name="dialog_button_amend_host_address">Amend</string>
<string name="dialog_button_back">@string/text_back</string>
<string name="dialog_button_cancel">Cancel</string>
<string name="dialog_button_cancel_download">Cancel</string>
@@ -369,7 +372,6 @@
<string name="dialog_button_confirm">OK</string>
<string name="dialog_button_connect">Connect</string>
<string name="dialog_button_continue">@string/text_continue</string>
<string name="dialog_button_continue_connecting">@string/dialog_button_continue</string>
<string name="dialog_button_copy">@string/text_copy</string>
<string name="dialog_button_default_prefix">Def prefix</string>
<string name="dialog_button_details">Details</string>
@@ -379,12 +381,12 @@
<string name="dialog_button_file_information">File info</string>
<string name="dialog_button_history">History</string>
<string name="dialog_button_ignore_current_update">Ignore</string>
<string name="dialog_button_interrupt_connection">Interrupt</string>
<string name="dialog_button_join_group">Join group</string>
<string name="dialog_button_manager">Manager</string>
<string name="dialog_button_more">More</string>
<string name="dialog_button_open_color_palette">Palette</string>
<string name="dialog_button_quit">Quit</string>
<string name="dialog_button_quit_connecting">@string/dialog_button_quit</string>
<string name="dialog_button_remove">Remove</string>
<string name="dialog_button_retrieve">Retrieve</string>
<string name="dialog_button_retry">Retry</string>
@@ -467,6 +469,8 @@
<string name="error_colon_must_follow_a_valid_ip_address">Colon must follow a valid IP address</string>
<string name="error_compass_cannot_be_null">Compass cannot be null</string>
<string name="error_connect_to_remote">Cannot connect to the remote server: %s</string>
<string name="error_connection_failed_with_host">Failed to connect to \"%1$s\".</string>
<string name="error_connection_timed_out">Connection timed out after %d ms</string>
<string name="error_continuation_resume_called_without_suspend">Method resume() should be called after suspend()</string>
<string name="error_continuation_suspend_called_more_than_once">Method suspend() should be only called once</string>
<string name="error_corresponding_github_release_may_not_published">Corresponding GitHub release may have not been published yet</string>
@@ -520,6 +524,7 @@
<string name="error_invalid_rest_compass">Invalid rest compass: %s</string>
<string name="error_invalid_selector_map_element" formatted="false">Invalid selector map element: { %s: %s (%s) }</string>
<string name="error_ip_address_should_not_be_empty">IP address can not be empty</string>
<string name="error_ipv6_port_requires_brackets">IPv6 port must be specified as \"[ipv6]:port\".</string>
<string name="error_max_listeners_exceeded">Max listeners exceeded: %d</string>
<string name="error_may_not_have_root_access_to_run_auto_file">AutoJs6 may not have root access to run \"auto\" file</string>
<string name="error_method_called_with_null_argument" formatted="false">%s() called with null argument: %s</string>
@@ -592,6 +597,7 @@
<string name="hint_long_click_run_to_debug">Long click \"Run\" button to debug</string>
<string name="hint_loop_delay">Delay before loop</string>
<string name="hint_loop_times">0 for infinite loop</string>
<string name="hint_pc_server_address_supported_formats">IPv4, IPv6 and domain are supported.</string>
<string name="instruction_install_plugin_from_url">Enter a URL pointing to a remote plugin address.\nFor example \"https://example.com/plugin.apk\".</string>
<string name="label_latest_used_time">Latest used: %1$s</string>
<string name="logger_ver_history_blob_thread_failure">\"Blob\" thread request failed</string>
@@ -816,7 +822,10 @@
<string name="text_connect_to_pc">Connect to PC</string>
<string name="text_connected">Connected</string>
<string name="text_connected_count_with_colon">Connected: %1$d</string>
<string name="text_connecting">Connecting</string>
<string name="text_connecting_to_host" tools:ignore="TypographyEllipsis">Connecting to \"%1$s\"...</string>
<string name="text_connection_cannot_be_established">Connection cannot be established</string>
<string name="text_connection_failed">Connection failed</string>
<string name="text_console">Console</string>
<string name="text_continue">Continue</string>
<string name="text_copy">Copy</string>
@@ -1409,6 +1418,7 @@
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Version name (computing...)</string>
<string name="text_view">View</string>
<string name="text_view_docs">View documents</string>
<string name="text_vivo_background_popup_permission">Background pop-ups</string>
<string name="text_vsc_ext_version_not_meet_requirement">The version of the VSCode extension does not meet the requirements</string>
<string name="text_waiting_for_all_data_processing_to_complete" tools:ignore="TypographyEllipsis">Waiting for all data processing to complete...</string>
<string name="text_weekly_task">Weekly task</string>
@@ -1416,5 +1426,6 @@
<string name="text_working_dir_path">Working directory path</string>
<string name="text_write_secure_settings">Write security settings</string>
<string name="text_write_system_settings">Write system settings</string>
<string name="text_xiaomi_background_popup_permission">Display pop-up windows while running in the background</string>
</resources>

View File

@@ -1,5 +1,5 @@
#Thu Jan 15 15:31:17 CST 2026
BUILD_TIME=1768462277223
#Fri Jan 16 17:47:33 CST 2026
BUILD_TIME=1768556853657
COMPILE_SDK_VERSION=36
IMAGE_QUANT_CMAKE_VERSION=3.22.1
IMAGE_QUANT_NDK_VERSION=26.1.10909125
@@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=36
TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3614
VERSION_BUILD=3620
VERSION_NAME=6.7.0 Alpha14
VSCODE_EXT_REQUIRED_VERSION=1.0.13