diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json index 50643337..d786ec46 100644 --- a/.changelog/lang_zh-Hans.json +++ b/.changelog/lang_zh-Hans.json @@ -36,6 +36,7 @@ "等价系列选择器 (UiSelector#id/text/...) 及包含系列选择器 (UiSelector#xxxContains) 支持正则表达式参数", "选择器的正则表达式参数支持使用标志 (i, m, s, u)", "正则表达式支持后瞻断言语法 _[`issue #464`](http://issues.autojs6.com/464)_", + "主页抽屉增加 \"指针位置\" 工具", "主页抽屉增加 \"所有文件管理权限\" 开关", "主页抽屉增加 \"后台弹出界面\" 开关 (针对 [小米/Vivo] 设备)", "设置页面增加 \"Java 原始类型包装\" 设置选项 _[`issue #435`](http://issues.autojs6.com/435)_", @@ -138,6 +139,7 @@ "客户端模式连接时支持连接状态显示及管理 (修正地址/中止连接)", "服务端模式连接时支持显示已建立连接的客户端数量", "浮动按钮增强后台启动 Activity 的安全性以避免应用崩溃", + "浮动按钮 \"更多\" 对话框使用异步加载数据方式提升显示流畅度", "浮动按钮 \"运行脚本\" 对话框增加 \"主页\" 菜单项", "浮动按钮 \"运行脚本\" 对话框支持最小化及状态恢复并尽最大努力保持窗口常驻或自动恢复", "使用 LiveData 及 SharedFlow 替代已弃用的 LocalBroadcastManager", diff --git a/app/src/main/java/org/autojs/autojs/app/CircularMenuOperationDialogBuilder.java b/app/src/main/java/org/autojs/autojs/app/CircularMenuOperationDialogBuilder.java index 2540fa0a..81e7554f 100644 --- a/app/src/main/java/org/autojs/autojs/app/CircularMenuOperationDialogBuilder.java +++ b/app/src/main/java/org/autojs/autojs/app/CircularMenuOperationDialogBuilder.java @@ -6,24 +6,42 @@ import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; - import org.autojs.autojs6.R; import org.autojs.autojs6.databinding.OperationDialogItemBinding; import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; /** * Created by Stardust on Jun 26, 2017. + * Modified by JetBrains AI Assistant (GPT-5.2) as of Jan 28, 2026. + * Modified by SuperMonster003 as of Jan 28, 2026. */ public class CircularMenuOperationDialogBuilder extends AppLevelThemeDialogBuilder { private final ArrayList mOnClickListeners = new ArrayList<>(); private final ArrayList mIcons = new ArrayList<>(); - private final ArrayList mTexts = new ArrayList<>(); + private final ArrayList mTitles = new ArrayList<>(); + private final ArrayList> mSubtitleSuppliers = new ArrayList<>(); + + private static final ExecutorService SUBTITLE_EXECUTOR = Executors.newFixedThreadPool(4); + + // Simple cache to avoid repeated calculations during dialog display (especially currentPackage/currentActivity). + // zh-CN: 简单缓存, 避免对话框展示期间重复计算 (尤其是 currentPackage/currentActivity). + private final Map mSubtitleCache = new ConcurrentHashMap<>(); + + // Token sequence to prevent misaligned updates caused by RecyclerView reuse. + // zh-CN: Token 序列, 防止 RecyclerView 复用导致错位更新. + private final AtomicLong mSubtitleRequestSeq = new AtomicLong(0); public CircularMenuOperationDialogBuilder(@NonNull Context context) { super(context); @@ -33,18 +51,90 @@ public class CircularMenuOperationDialogBuilder extends AppLevelThemeDialogBuild @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new ViewHolder(LayoutInflater.from(new ContextThemeWrapper(context, R.style.AppTheme)).inflate(R.layout.operation_dialog_item, parent, false)); + View itemView = LayoutInflater + .from(new ContextThemeWrapper(context, R.style.AppTheme)) + .inflate(R.layout.operation_dialog_item, parent, false); + return new ViewHolder(itemView); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.itemView.setOnClickListener(mOnClickListeners.get(position)); - holder.binding.text.setText(mTexts.get(position)); - holder.binding.text.setTextColor(context.getColor(R.color.day_night)); + + holder.binding.title.setText(mTitles.get(position)); + holder.binding.title.setTextColor(context.getColor(R.color.day_night)); + + var subtitleSupplier = mSubtitleSuppliers.get(position); + + // Cancel old task first (critical when holder is reused). + // zh-CN: 先取消旧任务 (holder 复用时很关键). + if (holder.subtitleFuture != null) { + holder.subtitleFuture.cancel(true); + holder.subtitleFuture = null; + } + + if (subtitleSupplier != null) { + holder.binding.subtitle.setVisibility(View.VISIBLE); + holder.binding.subtitle.setTextColor(context.getColor(R.color.day_night)); + + String cached = mSubtitleCache.get(position); + if (cached != null) { + holder.binding.subtitle.setText(cached); + } else { + holder.binding.subtitle.setText(context.getString(R.string.ellipsis_six)); + + // Record the token for this bind, validate it in the callback to avoid misaligned updates. + // zh-CN: 记录本次 bind 的 token, 回调时校验避免错位更新. + final long requestToken = mSubtitleRequestSeq.incrementAndGet(); + holder.subtitleRequestToken = requestToken; + + holder.subtitleFuture = SUBTITLE_EXECUTOR.submit(() -> { + String subtitle; + try { + subtitle = subtitleSupplier.get(); + } catch (Exception e) { + subtitle = "[ " + context.getString(R.string.error_an_error_occurred) + " ]"; + } + if (subtitle == null || subtitle.isEmpty()) { + subtitle = "[ " + context.getString(R.string.text_no_content) + " ]"; + } + + final String finalSubtitle = subtitle; + + // Cache the result (valid for the lifetime of this dialog). + // zh-CN: 缓存结果 (本次对话框生命周期内有效). + mSubtitleCache.put(position, finalSubtitle); + + // Return to main thread to update, and token validation is required. + // zh-CN: 回到主线程更新, 同时需要做 token 校验. + holder.itemView.post(() -> { + if (holder.subtitleRequestToken != requestToken) return; + // Prevent holder from being recycled or position from being invalid. + // zh-CN: 防止 holder 已被回收, 或 position 已失效. + if (holder.getBindingAdapterPosition() == RecyclerView.NO_POSITION) return; + holder.binding.subtitle.setText(finalSubtitle); + }); + }); + } + } else { + holder.binding.subtitle.setVisibility(View.GONE); + } + holder.binding.icon.setImageResource(mIcons.get(position)); holder.binding.icon.setImageTintList(ColorStateList.valueOf(context.getColor(R.color.day_night))); } + @Override + public void onViewRecycled(@NonNull ViewHolder holder) { + super.onViewRecycled(holder); + // Cancel task on recycle to avoid meaningless background work and callbacks that may update the wrong holder. + // zh-CN: 回收时取消任务, 避免无意义的后台工作以及回调可能会更新错误的 holder. + if (holder.subtitleFuture != null) { + holder.subtitleFuture.cancel(true); + holder.subtitleFuture = null; + } + } + @Override public int getItemCount() { return mOnClickListeners.size(); @@ -53,22 +143,31 @@ public class CircularMenuOperationDialogBuilder extends AppLevelThemeDialogBuild customView(operations, false); } - public CircularMenuOperationDialogBuilder item(int iconRes, int textRes) { - return item(iconRes, textRes, null); + public CircularMenuOperationDialogBuilder item(int iconRes, int titleRes) { + return item(iconRes, getContext().getString(titleRes), null, null); } - public CircularMenuOperationDialogBuilder item(int iconRes, int textRes, View.OnClickListener l) { - return item(iconRes, getContext().getString(textRes), l); + public CircularMenuOperationDialogBuilder item(int iconRes, int titleRes, View.OnClickListener l) { + return item(iconRes, getContext().getString(titleRes), null, l); } - public CircularMenuOperationDialogBuilder item(int iconRes, String text) { - return item(iconRes, text, null); + public CircularMenuOperationDialogBuilder item(int iconRes, String title) { + return item(iconRes, title, null, null); } - public CircularMenuOperationDialogBuilder item(int iconRes, String text, View.OnClickListener l) { + public CircularMenuOperationDialogBuilder item(int iconRes, String title, Supplier subtitleSupplier) { + return item(iconRes, title, subtitleSupplier, null); + } + + public CircularMenuOperationDialogBuilder item(int iconRes, String title, View.OnClickListener l) { + return item(iconRes, title, null, l); + } + + public CircularMenuOperationDialogBuilder item(int iconRes, String title, Supplier subtitleSupplier, View.OnClickListener l) { mOnClickListeners.add(l); mIcons.add(iconRes); - mTexts.add(text); + mTitles.add(title); + mSubtitleSuppliers.add(subtitleSupplier); return this; } @@ -77,10 +176,15 @@ public class CircularMenuOperationDialogBuilder extends AppLevelThemeDialogBuild @NonNull public final OperationDialogItemBinding binding; + public Future subtitleFuture; + + public long subtitleRequestToken; + public ViewHolder(View itemView) { super(itemView); binding = OperationDialogItemBinding.bind(itemView); } } + } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/app/tool/PointerLocationTool.kt b/app/src/main/java/org/autojs/autojs/app/tool/PointerLocationTool.kt new file mode 100644 index 00000000..90829d9f --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/app/tool/PointerLocationTool.kt @@ -0,0 +1,128 @@ +package org.autojs.autojs.app.tool + +import android.content.Context +import android.provider.Settings +import org.autojs.autojs.runtime.api.ProcessShell +import org.autojs.autojs.runtime.api.WrappedShizuku +import org.autojs.autojs.runtime.api.augment.device.Device.Companion.KEY_POINTER_LOCATION +import org.autojs.autojs.ui.main.drawer.ShowableItemHelper +import org.autojs.autojs.util.IntentUtils + +/** + * Created by SuperMonster003 on Jan 27, 2026. + */ +open class PointerLocationTool(final override val context: Context) : ShowableItemHelper { + + override val isShowing + get() = isPointerLocationEnabled(context) + + override fun show(): Boolean { + if (setPointerLocationEnabled(context)) { + return true + } + config() + return false + } + + override fun showIfNeeded() { + if (!isShowing) show() + } + + override fun hide(): Boolean { + if (setPointerLocationDisabled(context)) { + return true + } + config() + return false + } + + fun config() { + IntentUtils.launchDeveloperOptionsOrSettings(context) + } + + companion object { + + @JvmStatic + fun togglePointerLocation(context: Context): Boolean = + when (isPointerLocationDisabled(context)) { + true -> setPointerLocationEnabled(context) + else -> setPointerLocationDisabled(context) + } + + @JvmStatic + fun checkPointerLocationState(context: Context, aimState: Boolean) = + when (aimState) { + true -> isPointerLocationEnabled(context) + else -> isPointerLocationDisabled(context) + } + + @JvmStatic + fun setPointerLocationEnabled(context: Context): Boolean { + val command = "settings put system pointer_location ${PointerLocation.ENABLED.value}" + return when { + setPointerLocationStateByRoot(command) && isPointerLocationEnabled(context) -> true + setPointerLocationStateByShizuku(context, command) && isPointerLocationEnabled(context) -> true + else -> false + } + } + + @JvmStatic + fun setPointerLocationDisabled(context: Context): Boolean { + val command = "settings put system pointer_location ${PointerLocation.DISABLED.value}" + return when { + setPointerLocationStateByRoot(command) && isPointerLocationDisabled(context) -> true + setPointerLocationStateByShizuku(context, command) && isPointerLocationDisabled(context) -> true + else -> false + } + } + + fun isPointerLocationEnabled(context: Context) = getPointerLocationResult(context) == PointerLocation.ENABLED.value + + fun isPointerLocationDisabled(context: Context) = getPointerLocationResult(context) == PointerLocation.DISABLED.value + + fun getPointerLocationResult(context: Context): Int { + + val def = PointerLocation.DISABLED.value + val cmd = "settings get system pointer_location" + + runCatching bySettingsSystem@{ + Settings.System.getInt(context.contentResolver, KEY_POINTER_LOCATION, def) + }.onFailure { it.printStackTrace() }.getOrNull()?.let { return it } + + runCatching byRootShell@{ + // @Caution by SuperMonster003 on Mar 2, 2022. + // ! Result of execCommand() contains a "\n" and its length() of result is 2 not 1. + // ! zh-CN: 方法 execCommand() 返回值的 result 字段含有一个换行符, 且 result 的长度为 2 而非 1. + ProcessShell.execCommand(cmd, true).result.trim().toIntOrNull() + }.onFailure { it.printStackTrace() }.getOrNull()?.let { return it } + + runCatching byShizuku@{ + when { + WrappedShizuku.isOperational() -> { + WrappedShizuku.execCommand(context, cmd).result.trim().toIntOrNull() + } + else -> null + } + }.onFailure { it.printStackTrace() }.getOrNull()?.let { return it } + + return def + } + + private fun setPointerLocationStateByRoot(command: String) = runCatching { + ProcessShell.execCommand(command, true) + }.isSuccess + + private fun setPointerLocationStateByShizuku(context: Context, command: String) = runCatching { + WrappedShizuku.execCommand(context, command) + }.isSuccess + + enum class PointerLocation(val value: Int) { + ENABLED(1), + DISABLED(0) + } + + class StateChangedEvent + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/accessibility/AccessibilityTool.kt b/app/src/main/java/org/autojs/autojs/core/accessibility/AccessibilityTool.kt index 122b23e6..1f6b25cd 100644 --- a/app/src/main/java/org/autojs/autojs/core/accessibility/AccessibilityTool.kt +++ b/app/src/main/java/org/autojs/autojs/core/accessibility/AccessibilityTool.kt @@ -1,6 +1,5 @@ package org.autojs.autojs.core.accessibility -import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.os.Build @@ -23,7 +22,7 @@ import org.autojs.autojs6.R /** * Created by Stardust on Jan 26, 2017. - * Modified by SuperMonster003 as of Feb 15, 2022. + * Modified by SuperMonster003 as of Jan 28, 2026. */ class AccessibilityTool(private val context: Context? = null) { @@ -43,13 +42,14 @@ class AccessibilityTool(private val context: Context? = null) { } @ScriptInterface - fun launchSettings() { - "${mContext.getString(R.string.text_please_choose)} ${mContext.getString(R.string.app_name)}".let { - ViewUtils.showToast(mContext, it, true) + @JvmOverloads + fun launchSettings(showGuideMessage: Boolean = true, showExceptionHint: Boolean = true) { + if (showGuideMessage) { + val msg = "${mContext.getString(R.string.text_please_choose)} ${mContext.getString(R.string.app_name)}" + ViewUtils.showToast(mContext, msg, true) } - try { - Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).startSafely(mApplicationContext) - } catch (_: ActivityNotFoundException) { + val result = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).startSafely(mApplicationContext) + if (!result && showExceptionHint) { ViewUtils.showToast(mContext, R.string.go_to_accessibility_settings, true) } } @@ -82,6 +82,9 @@ class AccessibilityTool(private val context: Context? = null) { @ScriptInterface fun isOperational() = isRunning() && AccessibilityService.hasOperationalState + @ScriptInterface + fun isMalfunctioning() = hasService() && !hasInstance() + @JvmOverloads @ScriptInterface fun startService(withLaunchSettings: Boolean = true): Boolean { diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/WrappedShizuku.kt b/app/src/main/java/org/autojs/autojs/runtime/api/WrappedShizuku.kt index cc795f5b..3f086700 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/WrappedShizuku.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/WrappedShizuku.kt @@ -32,7 +32,7 @@ object WrappedShizuku { @JvmField var service: IUserService? = null - private val TAG: String = WrappedShizuku::class.java.simpleName + private const val TAG = "WrappedShizuku" private val mRequestCode = when { isInrt -> "shizuku-request-code-inrt".hashCode() @@ -139,11 +139,14 @@ object WrappedShizuku { } @ScriptInterface - fun isOperational() = isRunning() && hasPermission() + fun isOperational() = hasService() && isRunning() && hasPermission() @ScriptInterface fun isRunning() = mHasBinder + @ScriptInterface + fun hasService() = service != null + @ScriptInterface fun requestPermission() = Shizuku.requestPermission(mRequestCode) @@ -170,7 +173,7 @@ object WrappedShizuku { } private fun execCommandWithAutoReconnect(context: Context, cmd: String, allowRetry: Boolean): ShellResult { - if (service == null && hasPermission()) { + if (!hasService() && hasPermission()) { onCreate() bindUserServiceIfNeeded() initializeShizukuServiceAndWait(5000L) @@ -189,6 +192,7 @@ object WrappedShizuku { ) ) } catch (e: Throwable) { + Log.d(TAG, "execCommand failed", e) // Reconnect and retry once when binder is dead. // zh-CN: 当 binder 已死亡时, 自动重连并重试一次. if (allowRetry && e is DeadObjectException) { diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/device/Device.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/device/Device.kt index e3df0b47..8b57c6b5 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/augment/device/Device.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/device/Device.kt @@ -1,14 +1,14 @@ package org.autojs.autojs.runtime.api.augment.device -import android.provider.Settings import androidx.core.net.toUri import org.autojs.autojs.annotation.RhinoFunctionBody import org.autojs.autojs.annotation.RhinoRuntimeFunctionInterface -import org.autojs.autojs.rhino.extension.AnyExtensions.isJsNullish -import org.autojs.autojs.rhino.extension.AnyExtensions.jsBrief +import org.autojs.autojs.app.tool.PointerLocationTool import org.autojs.autojs.rhino.ArgumentGuards import org.autojs.autojs.rhino.ArgumentGuards.Companion.component1 import org.autojs.autojs.rhino.ArgumentGuards.Companion.component2 +import org.autojs.autojs.rhino.extension.AnyExtensions.isJsNullish +import org.autojs.autojs.rhino.extension.AnyExtensions.jsBrief import org.autojs.autojs.runtime.ScriptRuntime import org.autojs.autojs.runtime.api.ScreenMetrics import org.autojs.autojs.runtime.api.augment.Augmentable @@ -19,8 +19,6 @@ import org.autojs.autojs.util.DeviceUtils import org.autojs.autojs.util.NetworkUtils import org.autojs.autojs.util.RhinoUtils.UNDEFINED import org.autojs.autojs.util.RhinoUtils.coerceBoolean -import org.autojs.autojs.util.ShellUtils -import org.autojs.autojs.util.ShellUtils.PointerLocation import org.mozilla.javascript.Context import org.mozilla.javascript.NativeArray import org.mozilla.javascript.Undefined @@ -124,14 +122,14 @@ class Device(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { @JvmStatic @RhinoFunctionBody fun vibrateRhinoWithRuntime(scriptRuntime: ScriptRuntime, o: Any?, p: Any?) { - when { - o is String -> { + when (o) { + is String -> { MorseCode.vibrateRhino(o, p) } - o is Number && p is Number -> { + is Number if p is Number -> { scriptRuntime.device.vibrate(/* off = */ o.toLong(), /* millis = */ p.toLong()) } - o is NativeArray && p is Number -> { + is NativeArray if p is Number -> { val listOff = listOf(p.toLong()) val listOthers = o.map { toVibrateTimingElement(it) } val timings = (listOff + listOthers).toLongArray() @@ -253,7 +251,7 @@ class Device(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { // # } // # } - ShellUtils.checkPointerLocationState(globalContext, enabled) || ShellUtils.togglePointerLocation(globalContext) + PointerLocationTool.checkPointerLocationState(globalContext, enabled) || PointerLocationTool.togglePointerLocation(globalContext) } @JvmStatic @@ -271,17 +269,13 @@ class Device(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { @JvmStatic @RhinoRuntimeFunctionInterface fun isPointerLocationEnabled(scriptRuntime: ScriptRuntime, args: Array): Boolean = ensureArgumentsIsEmpty(args) { - try { - Settings.System.getInt(globalContext.contentResolver, KEY_POINTER_LOCATION, PointerLocation.DISABLED.value) == PointerLocation.ENABLED.value - } catch (e: Exception) { - ShellUtils.checkPointerLocationState(globalContext, true) - } + PointerLocationTool.isPointerLocationEnabled(globalContext) } @JvmStatic @RhinoRuntimeFunctionInterface fun isPointerLocationDisabled(scriptRuntime: ScriptRuntime, args: Array): Boolean = ensureArgumentsIsEmpty(args) { - !isPointerLocationEnabled(scriptRuntime, args) + PointerLocationTool.isPointerLocationDisabled(globalContext) } @JvmStatic diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java b/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java index a283fb84..16a0b279 100644 --- a/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java +++ b/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java @@ -15,6 +15,7 @@ import org.autojs.autojs.AutoJs; import org.autojs.autojs.app.AppLevelThemeDialogBuilder; import org.autojs.autojs.app.CircularMenuOperationDialogBuilder; import org.autojs.autojs.app.DialogUtils; +import org.autojs.autojs.app.tool.PointerLocationTool; import org.autojs.autojs.core.accessibility.AccessibilityTool; import org.autojs.autojs.core.accessibility.Capture; import org.autojs.autojs.core.accessibility.LayoutInspector; @@ -38,7 +39,6 @@ import org.autojs.autojs.ui.floating.layoutinspector.LayoutHierarchyFloatyWindow import org.autojs.autojs.ui.main.MainActivity; import org.autojs.autojs.util.ClipboardUtils; import org.autojs.autojs.util.RootUtils; -import org.autojs.autojs.util.ShellUtils; import org.autojs.autojs.util.ViewUtils; import org.autojs.autojs.util.WorkingDirectoryUtils; import org.autojs.autojs6.R; @@ -75,14 +75,15 @@ public class CircularMenu implements LayoutInspector.CaptureAvailableListener { private final GlobalActionRecorder mRecorder; private final Recorder.OnStateChangedListener mRecorderStateListener; private CircularActionMenuBinding binding; - private MaterialDialog mSettingsDialog; + private MaterialDialog mMenuOperationDialog; private MaterialDialog mScriptListDialog; private ExplorerView mScriptListDialogExplorerView; private MaterialDialog mLayoutInspectDialog; - private String mRunningPackage; - private String mRunningActivity; + private String mCurrentPackage; + private String mCurrentActivity; private Deferred mCaptureDeferred; private final AccessibilityTool mA11yTool; + private final PointerLocationTool mPointerLocationTool; private final View.OnClickListener mCollapseWindowAndInspectLayoutBoundsListener = v -> { mWindow.collapse(); @@ -124,6 +125,7 @@ public class CircularMenu implements LayoutInspector.CaptureAvailableListener { mRecorder.addOnStateChangedListener(mRecorderStateListener); AutoJs.getInstance().getLayoutInspector().addCaptureAvailableListener(this); mA11yTool = new AccessibilityTool(mContext); + mPointerLocationTool = new PointerLocationTool(mContext); } private void setupWindowListeners() { @@ -267,65 +269,59 @@ public class CircularMenu implements LayoutInspector.CaptureAvailableListener { binding.actionMenuMore.setOnClickListener(v -> { mWindow.collapse(); - if (mSettingsDialog != null && mSettingsDialog.isShowing()) { - mSettingsDialog.dismiss(); + if (mMenuOperationDialog != null && mMenuOperationDialog.isShowing()) { + mMenuOperationDialog.dismiss(); } - applyComponentInformation(); - - // noinspection CodeBlock2Expr - mSettingsDialog = new CircularMenuOperationDialogBuilder(mContext) - .item(R.drawable.ic_accessibility_black_48dp, mContext.getString(R.string.text_manage_a11y_service), onCircularMenuItemClick(v1 -> { - mA11yTool.launchSettings(); + mMenuOperationDialog = new CircularMenuOperationDialogBuilder(mContext) + .item(R.drawable.ic_accessibility_black_48dp, mContext.getString(R.string.text_a11y_service), this::getA11yState, onCircularMenuItemClick(itemView -> { + mA11yTool.launchSettings(false, true); })) - .item(R.drawable.ic_text_fields_black_48dp, mContext.getString(R.string.text_latest_package) + ":\n" + getRunningPackage(), onCircularMenuItemClick(v1 -> { - if (!TextUtils.isEmpty(mRunningPackage)) { - ClipboardUtils.setClip(mContext, mRunningPackage); + .item(R.drawable.ic_text_fields_black_48dp, mContext.getString(R.string.text_latest_package), this::getCurrentPackage, onCircularMenuItemClick(itemView -> { + if (!TextUtils.isEmpty(mCurrentPackage)) { + ClipboardUtils.setClip(mContext, mCurrentPackage); ViewUtils.showToast(mContext, getTextAlreadyCopied(R.string.text_latest_package)); } })) - .item(R.drawable.ic_text_fields_black_48dp, mContext.getString(R.string.text_latest_activity) + ":\n" + getRunningActivity(), onCircularMenuItemClick(v1 -> { - if (!TextUtils.isEmpty(mRunningActivity)) { - ClipboardUtils.setClip(mContext, mRunningActivity); + .item(R.drawable.ic_text_fields_black_48dp, mContext.getString(R.string.text_latest_activity), this::getCurrentActivity, onCircularMenuItemClick(itemView -> { + if (!TextUtils.isEmpty(mCurrentActivity)) { + ClipboardUtils.setClip(mContext, mCurrentActivity); ViewUtils.showToast(mContext, getTextAlreadyCopied(R.string.text_latest_activity)); } })) - .item(R.drawable.ic_home_black_48dp, mContext.getString(R.string.text_open_main_activity), onCircularMenuItemClick(v1 -> { + .item(R.drawable.ic_home_black_48dp, mContext.getString(R.string.text_open_main_activity), onCircularMenuItemClick(itemView -> { MainActivity.launch(mContext); })) - .item(R.drawable.ic_control_point_black_48dp, mContext.getString(R.string.text_pointer_location), onCircularMenuItemClick(v1 -> { - if (!ShellUtils.togglePointerLocation(mContext)) { - ViewUtils.showToast(mContext, mContext.getString(R.string.text_pointer_location_toggle_failed_with_hint), true); + .item(R.drawable.ic_control_point_black_48dp, mContext.getString(R.string.text_pointer_location), this::getPointerLocationState, onCircularMenuItemClick(itemView -> { + if (PointerLocationTool.togglePointerLocation(mContext)) { + // var subtitleView = itemView.findViewById(R.id.subtitle); + // if (subtitleView instanceof TextView textView) { + // textView.setText(mPointerLocationTool.isShowing() ? mContext.getString(R.string.text_enabled) : mContext.getString(R.string.text_disabled)); + // } + EventBus.getDefault().post(new PointerLocationTool.Companion.StateChangedEvent()); + return; } + // ViewUtils.showToast(mContext, mContext.getString(R.string.text_pointer_location_toggle_failed_with_hint), true); + mPointerLocationTool.config(); })) - .item(R.drawable.ic_close_white_48dp, mContext.getString(R.string.text_close_floating_button), onCircularMenuItemClick(v1 -> { + .item(R.drawable.ic_close_white_48dp, mContext.getString(R.string.text_close_floating_button), onCircularMenuItemClick(itemView -> { closeAndSaveState(false); })) .title(mContext.getString(R.string.text_more)) .build(); - DialogUtils.showAdaptive(mSettingsDialog); + DialogUtils.showAdaptive(mMenuOperationDialog); }); } - private void applyComponentInformation() { - if (WrappedShizuku.INSTANCE.isOperational() && WrappedShizuku.service != null) { - try { - mRunningPackage = WrappedShizuku.service.currentPackage(); - mRunningActivity = WrappedShizuku.service.currentActivity(); - return; - } catch (RemoteException ignored) { + @NonNull + private String getA11yState() { + return mA11yTool.isMalfunctioning() ? mContext.getString(R.string.text_malfunctioning) : mA11yTool.isRunning() ? mContext.getString(R.string.text_enabled) : mContext.getString(R.string.text_disabled); + } - } - } - if (RootUtils.isRootAvailable()) { - mRunningPackage = Shell.currentPackageRhino(); - mRunningActivity = Shell.currentActivityRhino(); - return; - } - ActivityInfoProvider infoProvider = AutoJs.getInstance().getInfoProvider(); - mRunningPackage = infoProvider.getLatestPackageByUsageStatsIfGranted(); - mRunningActivity = infoProvider.getLatestActivity(); + @NonNull + private String getPointerLocationState() { + return mPointerLocationTool.isShowing() ? mContext.getString(R.string.text_enabled) : mContext.getString(R.string.text_disabled); } @NonNull @@ -415,16 +411,52 @@ public class CircularMenu implements LayoutInspector.CaptureAvailableListener { mWindow.savePosition(newConfig); } - private String getRunningPackage() { - if (!TextUtils.isEmpty(mRunningPackage)) { - return mRunningPackage; + private String getCurrentPackage() { + if (WrappedShizuku.INSTANCE.isOperational()) { + try { + mCurrentPackage = Objects.requireNonNull(WrappedShizuku.service).currentPackage(); + if (!TextUtils.isEmpty(mCurrentPackage)) { + return mCurrentPackage; + } + } catch (RemoteException ignored) { + /* Ignored. */ + } + } + if (RootUtils.isRootAvailable()) { + mCurrentPackage = Shell.currentPackageRhino(); + if (!TextUtils.isEmpty(mCurrentPackage)) { + return mCurrentPackage; + } + } + ActivityInfoProvider infoProvider = AutoJs.getInstance().getInfoProvider(); + mCurrentPackage = infoProvider.getLatestPackageByUsageStatsIfGranted(); + if (!TextUtils.isEmpty(mCurrentPackage)) { + return mCurrentPackage; } return getEmptyInfoHint(); } - private String getRunningActivity() { - if (!TextUtils.isEmpty(mRunningActivity)) { - return mRunningActivity; + private String getCurrentActivity() { + if (WrappedShizuku.INSTANCE.isOperational()) { + try { + mCurrentActivity = Objects.requireNonNull(WrappedShizuku.service).currentActivity(); + if (!TextUtils.isEmpty(mCurrentActivity)) { + return mCurrentActivity; + } + } catch (RemoteException ignored) { + /* Ignored. */ + } + } + if (RootUtils.isRootAvailable()) { + mCurrentActivity = Shell.currentActivityRhino(); + if (!TextUtils.isEmpty(mCurrentActivity)) { + return mCurrentActivity; + } + } + ActivityInfoProvider infoProvider = AutoJs.getInstance().getInfoProvider(); + mCurrentActivity = infoProvider.getLatestActivity(); + if (!TextUtils.isEmpty(mCurrentActivity)) { + return mCurrentActivity; } return getEmptyInfoHint(); } @@ -443,9 +475,9 @@ public class CircularMenu implements LayoutInspector.CaptureAvailableListener { } private void dismissSettingsDialog() { - if (mSettingsDialog != null) { - mSettingsDialog.dismiss(); - mSettingsDialog = null; + if (mMenuOperationDialog != null) { + mMenuOperationDialog.dismiss(); + mMenuOperationDialog = null; } } diff --git a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.kt b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.kt index 52798f61..e5310494 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.kt @@ -17,6 +17,7 @@ import org.autojs.autojs.AutoJs import org.autojs.autojs.app.tool.FloatingButtonTool import org.autojs.autojs.app.tool.JsonSocketClientTool import org.autojs.autojs.app.tool.JsonSocketServerTool +import org.autojs.autojs.app.tool.PointerLocationTool import org.autojs.autojs.core.accessibility.AccessibilityTool import org.autojs.autojs.core.plugin.center.PluginCenterActivity import org.autojs.autojs.core.pref.Pref @@ -100,6 +101,7 @@ open class DrawerFragment : Fragment() { private lateinit var mAccessibilityServiceItem: DrawerMenuToggleableItem private lateinit var mForegroundServiceItem: DrawerMenuToggleableItem private lateinit var mFloatingButtonItem: DrawerMenuToggleableItem + private lateinit var mPointerLocationItem: DrawerMenuToggleableItem private lateinit var mClientModeItem: DrawerMenuDisposableItem private lateinit var mServerModeItem: DrawerMenuDisposableItem private lateinit var mNotificationPostItem: DrawerMenuToggleableItem @@ -144,7 +146,7 @@ open class DrawerFragment : Fragment() { override fun refreshSubtitle(aimState: Boolean) { val oldSubtitle = mAccessibilityServiceItem.subtitle if (aimState) { - if (mA11yTool.hasService() && !mA11yTool.isRunning()) { + if (mA11yTool.isMalfunctioning()) { mAccessibilityServiceItem.subtitle = context.getString(R.string.text_malfunctioning) } else { mAccessibilityServiceItem.subtitle = null @@ -216,7 +218,19 @@ open class DrawerFragment : Fragment() { prefKey = R.string.key_floating_menu_shown, ).also { item -> item.setOnLaunchSettingsListener { - val helper = item.getHelper() as DisplayOverOtherAppsPermission + val helper = DisplayOverOtherAppsPermission(mContext) + helper.config() + } + } + + mPointerLocationItem = DrawerMenuToggleableItem( + helper = PointerLocationTool(mContext), + icon = R.drawable.ic_control_point_bigger_black_48dp, + title = R.string.text_pointer_location, + descriptionRes = R.string.description_pointer_location, + ).also { item -> + item.setOnLaunchSettingsListener { + val helper = item.getHelper() as PointerLocationTool helper.config() } } @@ -636,6 +650,12 @@ open class DrawerFragment : Fragment() { // mFloatingWindowItem.toggle(event.currentState != CircularMenu.STATE_CLOSED) } + @Suppress("unused", "UNUSED_PARAMETER") + @Subscribe + fun onPointerLocationStateChange(event: PointerLocationTool.Companion.StateChangedEvent) { + mPointerLocationItem.sync() + } + @Subscribe @Suppress("unused", "UNUSED_PARAMETER") fun onDrawerOpened(event: Event.OnDrawerOpened) { @@ -673,6 +693,7 @@ open class DrawerFragment : Fragment() { mForegroundServiceItem, DrawerMenuGroup(R.string.text_tools), mFloatingButtonItem, + mPointerLocationItem, DrawerMenuGroup(R.string.text_connect_to_pc), mClientModeItem, mServerModeItem, @@ -723,6 +744,7 @@ open class DrawerFragment : Fragment() { mAccessibilityServiceItem, mForegroundServiceItem, mFloatingButtonItem, + mPointerLocationItem, mClientModeItem, mServerModeItem, mNotificationPostItem, diff --git a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerMenuToggleableItem.kt b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerMenuToggleableItem.kt index 1eef4332..bac131aa 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerMenuToggleableItem.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerMenuToggleableItem.kt @@ -95,7 +95,14 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem { onNeutral { d, _ -> runCatching { listener(d) - }.getOrNull() ?: ViewUtils.showSnack(d.view, R.string.error_failed_to_launch_manager) + }.onFailure { e -> + MaterialDialog.Builder(itemHelper.context) + .title(R.string.error_failed_to_launch_manager) + .apply { e.message?.let { content(it) } } + .positiveText(R.string.dialog_button_dismiss) + .positiveColorRes(R.color.dialog_button_default) + .show() + } } } mOnLaunchSettingsListener?.let { listener -> @@ -104,7 +111,14 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem { onPositive { d, _ -> runCatching { listener(d) - }.getOrNull() ?: ViewUtils.showSnack(d.view, R.string.error_failed_to_launch_system_settings) + }.onFailure { e -> + MaterialDialog.Builder(itemHelper.context) + .title(R.string.error_failed_to_launch_system_settings) + .apply { e.message?.let { content(it) } } + .positiveText(R.string.dialog_button_dismiss) + .positiveColorRes(R.color.dialog_button_default) + .show() + } } } } diff --git a/app/src/main/java/org/autojs/autojs/util/IntentUtils.kt b/app/src/main/java/org/autojs/autojs/util/IntentUtils.kt index 345eb3d7..6e95b50b 100644 --- a/app/src/main/java/org/autojs/autojs/util/IntentUtils.kt +++ b/app/src/main/java/org/autojs/autojs/util/IntentUtils.kt @@ -276,6 +276,28 @@ object IntentUtils { return result } + @JvmStatic + fun launchDeveloperOptions(context: Context): Boolean { + val intent = Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS) + + return if (intent.resolveActivity(context.packageManager) != null) { + intent.startSafely(context) + } else { + false + } + } + + @JvmStatic + fun launchDeveloperOptionsOrSettings(context: Context) { + val intents = listOf( + Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS), + Intent(Settings.ACTION_SETTINGS), + ) + + val pm = context.packageManager + intents.firstOrNull { it.resolveActivity(pm) != null }?.startSafely(context) + } + fun requestAppUsagePermission(context: Context) = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS) .startSafely(context, true) diff --git a/app/src/main/java/org/autojs/autojs/util/ShellUtils.kt b/app/src/main/java/org/autojs/autojs/util/ShellUtils.kt deleted file mode 100644 index 2fe876ad..00000000 --- a/app/src/main/java/org/autojs/autojs/util/ShellUtils.kt +++ /dev/null @@ -1,63 +0,0 @@ -package org.autojs.autojs.util - -import android.content.Context -import org.autojs.autojs.runtime.api.ProcessShell -import org.autojs.autojs.runtime.api.WrappedShizuku - -object ShellUtils { - - @JvmStatic - fun togglePointerLocation(context: Context): Boolean { - val aimState = !isPointerLocationEnabled(context) - val command = "settings put system pointer_location " + when (aimState) { - true -> PointerLocation.ENABLED.value - else -> PointerLocation.DISABLED.value - } - return when { - setPointerLocationStateByRoot(command) && checkPointerLocationState(context, aimState) -> true - setPointerLocationStateByShizuku(context, command) && checkPointerLocationState(context, aimState) -> true - else -> false - } - } - - @JvmStatic - fun checkPointerLocationState(context: Context, aimState: Boolean) = when (aimState) { - true -> isPointerLocationEnabled(context) - else -> isPointerLocationDisabled(context) - } - - private fun isPointerLocationEnabled(context: Context) = getPointerLocationResult(context) == PointerLocation.ENABLED.value - - private fun isPointerLocationDisabled(context: Context) = getPointerLocationResult(context) == PointerLocation.DISABLED.value - - private fun getPointerLocationResult(context: Context): Int { - val cmd = "settings get system pointer_location" - return try { - // @Caution by SuperMonster003 on Mar 2, 2022. - // ! Result of execCommand() contains a "\n" and its length() of result is 2 not 1. - // ! zh-CN: 方法 execCommand() 返回值的 result 字段含有一个换行符, 且 result 的长度为 2 而非 1. - ProcessShell.execCommand(cmd, true).result.trim().toInt() - } catch (e: Exception) { - e.printStackTrace() - try { - WrappedShizuku.execCommand(context, cmd).result.trim().toInt() - } catch (e: Exception) { - PointerLocation.DISABLED.value.also { e.printStackTrace() } - } - } - } - - private fun setPointerLocationStateByRoot(command: String) = runCatching { - ProcessShell.execCommand(command, true) - }.isSuccess - - private fun setPointerLocationStateByShizuku(context: Context, command: String) = runCatching { - WrappedShizuku.execCommand(context, command) - }.isSuccess - - enum class PointerLocation(val value: Int) { - ENABLED(1), - DISABLED(0) - } - -} diff --git a/app/src/main/res/drawable-xhdpi/ic_control_point_bigger_black_48dp.png b/app/src/main/res/drawable-xhdpi/ic_control_point_bigger_black_48dp.png new file mode 100644 index 00000000..3368ac89 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_control_point_bigger_black_48dp.png differ diff --git a/app/src/main/res/layout/operation_dialog_item.xml b/app/src/main/res/layout/operation_dialog_item.xml index e48d4e8b..562a9384 100644 --- a/app/src/main/res/layout/operation_dialog_item.xml +++ b/app/src/main/res/layout/operation_dialog_item.xml @@ -1,23 +1,31 @@ - + + android:id="@+id/icon" + android:translationY="1dp" + android:layout_width="20dp" + android:layout_height="20dp" + tools:src="@drawable/ic_control_point_black_48dp" /> - + + + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 3c01cdec..74bf888a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -1200,5 +1200,7 @@ الصفحة الرئيسية تعذّر تغيير حالة المفتاح لضمان عمل خدمات المقدّمة في AutoJs6 وغيرها بشكل طبيعي، وتمكين السكربتات من نشر الإشعارات، يجب منح AutoJs6 إذن \"نشر الإشعارات\". + \"موقع المؤشر\" هي ميزة تصحيح أخطاء ضمن خيارات المطوّر في Android.\nعند تفعيلها، يعرض النظام على الشاشة معلومات نقاط اللمس مثل [الإحداثيات/مسار الحركة/العدد/الحجم/سرعة الحركة/الضغط]، مما يسهل [كتابة/تصحيح/التحقق من] السكربتات ذات الصلة. + حدث خطأ diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index c6ff0fa7..975352d6 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -1195,5 +1195,7 @@ Homepage Failed to change the toggle state To ensure that AutoJs6 foreground services, etc. can work properly and that scripts can post notifications, AutoJs6 must be granted the \"post notifications\" permission. + \"Pointer location\" is a debugging feature in Android Developer options.\nWhen enabled, the system will display information about touch point(s) on the screen, such as [coordinates/movement trajectory/count/size/movement speed/pressure], which helps with [writing/debugging/verification] of related scripts. + An error occurred diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3ce75861..a589496c 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1198,5 +1198,7 @@ Inicio No se pudo cambiar el estado del interruptor Para garantizar que los servicios en primer plano de AutoJs6, etc. funcionen correctamente y que los scripts puedan publicar notificaciones, AutoJs6 debe recibir el permiso de \"publicar notificaciones\". + \"Ubicación del puntero\" es una función de depuración en las opciones de desarrollador de Android.\nAl activarla, el sistema mostrará en pantalla información del/de los punto(s) de toque, como [coordenadas/trayectoria de movimiento/cantidad/tamaño/velocidad de movimiento/presión], lo que facilita la [escritura/depuración/verificación] de los scripts relacionados. + Se produjo un error diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b5bc65d1..6d77e9b9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1198,5 +1198,7 @@ Accueil Échec de la modification de l\'état de l\'interrupteur Afin de garantir que les services au premier plan d\'AutoJs6, etc. fonctionnent correctement et que les scripts puissent publier des notifications, AutoJs6 doit se voir accorder l\'autorisation de \"publier des notifications\". + \"Emplacement du pointeur\" est une fonctionnalité de débogage dans les options pour les développeurs d\'Android.\nUne fois activée, le système affiche à l\'écran des informations sur le(s) point(s) de contact, telles que [coordonnées/trajectoire de déplacement/nombre/taille/vitesse de déplacement/pression], ce qui facilite [l\'écriture/le débogage/la vérification] des scripts associés. + Une erreur s\'est produite diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index abd0ad20..88f276b6 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1199,5 +1199,7 @@ ホームページ スイッチの状態の変更に失敗しました AutoJs6 のフォアグラウンドサービス等が正常に動作し, スクリプトが通知を投稿できるようにするため, AutoJs6 には \"通知の送信\" 権限を付与する必要があります. + \"ポインタの位置\" は Android の開発者向けオプションにあるデバッグ機能です.\n有効にすると, システムが画面上にタッチポイントの [座標/移動軌跡/数/サイズ/移動速度/圧力] などの情報を表示し, 関連スクリプトの [作成/デバッグ/検証] に役立ちます. + エラーが発生しました diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index aa818776..df0338f0 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1200,5 +1200,7 @@ 홈페이지 스위치 상태 변경에 실패했습니다 AutoJs6 포그라운드 서비스 등이 정상적으로 동작하고 스크립트가 알림을 게시할 수 있도록 하려면, AutoJs6 에 \"알림 게시\" 권한을 부여해야 합니다. + \"포인터 위치\" 는 Android 개발자 옵션에 있는 디버깅 기능입니다.\n활성화하면 시스템이 화면에 터치 지점의 [좌표/이동 궤적/개수/크기/이동 속도/압력] 등의 정보를 표시하여 관련 스크립트의 [작성/디버깅/검증] 에 도움이 됩니다. + 오류가 발생했습니다 diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 17eceff5..604b688d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -68,7 +68,7 @@ /Сценарии AutoJs6 - это автоматизированный инструмент JavaScript для android, который является открытым исходным кодом и клонирован из \"hyb1996/Auto.js\".\n\nЧтобы попросить о помощи или обратной связи, посетите GitHub issues или присоединитесь к группе Tencent QQ 690946137. 1. Нажмите и удерживайте значок приложения на странице \"О приложении и разработчике\", чтобы перейти на страницу параметров разработчика AutoJs6. - Служба доступности — ключевая возможность AutoJs6 для автоматизации. Она используется для чтения информации об элементах интерфейса на экране и для имитации взаимодействий, таких как [ нажатие / свайп / ввод ].\n\nБольшинство функций, связанных с автоматизацией, а также инструменты вроде анализа макета, требуют включения службы доступности для корректной работы. + Служба доступности - ключевая возможность AutoJs6 для автоматизации. Она используется для чтения информации об элементах интерфейса на экране и для имитации взаимодействий, таких как [ нажатие / свайп / ввод ].\n\nБольшинство функций, связанных с автоматизацией, а также инструменты вроде анализа макета, требуют включения службы доступности для корректной работы. Разрешение \"управление всеми файлами\" (или \"доступ ко всем файлам\") позволяет AutoJs6 напрямую [ создавать / читать / изменять / удалять ] файлы в общем хранилище через обычные пути файлов, чтобы скрипты могли получать доступ к \"Internal Storage\", а файловый менеджер мог корректно отображать и управлять файлами.\n\nНа устройствах Android 11+ это основной способ обеспечить полный доступ к чтению/записи файлов. Это предпочтение для изменения языка отображения AutoJs6, включая сообщения исключений от запущенных скриптов.\n\nПримечание: может потребоваться перезапуск приложения, чтобы язык был применен так, как ожидается. После включения автоматического ночного режима AutoJs6 будет автоматически переключать ночной режим в соответствии с настройками системы.\n\nNote: переключатель автоматического ночного режима и переключатель ночного режима связаны и взаимно влияют друг на друга. @@ -1198,5 +1198,7 @@ Главная Не удалось изменить состояние переключателя Чтобы обеспечить корректную работу фоновых служб переднего плана AutoJs6 и возможность публикации уведомлений скриптами, AutoJs6 необходимо предоставить разрешение \"публикации уведомлений\". + \"Положение указателя\" - это отладочная функция в параметрах разработчика Android.\nПосле включения система будет отображать на экране сведения о точке(ах) касания, такие как [координаты/траектория движения/количество/размер/скорость движения/давление], что упрощает [написание/отладку/проверку] соответствующих скриптов. + Произошла ошибка diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 8b3603dc..d979787c 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1196,5 +1196,7 @@ 主頁 開關狀態改變失敗 為確保 AutoJs6 前台服務等能夠正常運行, 腳本能夠發佈通知, AutoJs6 需要被授予 \"發佈通知權限\". + \"指針位置\" 是安卓開發者選項中的調試功能.\n開啓後, 系統會在屏幕上顯示觸摸點的 [座標/移動軌跡/數量/大小/移動速度/壓力] 等信息, 便於相關腳本的 [編寫/調試/校對] 等. + 發生錯誤 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a20358b5..d5852dbb 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1196,5 +1196,7 @@ 主頁 開關狀態改變失敗 為確保 AutoJs6 前臺服務等能夠正常執行, 指令碼能夠釋出通知, AutoJs6 需要被授予 \"釋出通知許可權\". + \"指標位置\" 是安卓開發者選項中的除錯功能.\n開啟後, 系統會在螢幕上顯示觸控點的 [座標/移動軌跡/數量/大小/移動速度/壓力] 等資訊, 便於相關指令碼的 [編寫/除錯/校對] 等. + 發生錯誤 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c6b00461..17c50ca0 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1196,5 +1196,7 @@ 主页 开关状态改变失败 为确保 AutoJs6 前台服务等能够正常运行, 脚本能够发布通知, AutoJs6 需要被授予 \"发布通知权限\". + \"指针位置\" 是安卓开发者选项中的调试功能.\n开启后, 系统会在屏幕上显示触摸点的 [坐标/移动轨迹/数量/大小/移动速度/压力] 等信息, 便于相关脚本的 [编写/调试/校对] 等. + 发生错误 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa8a74e1..e36eea99 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1455,5 +1455,7 @@ Homepage Failed to change the toggle state To ensure that AutoJs6 foreground services, etc. can work properly and that scripts can post notifications, AutoJs6 must be granted the \"post notifications\" permission. + \"Pointer location\" is a debugging feature in Android Developer options.\nWhen enabled, the system will display information about touch point(s) on the screen, such as [coordinates/movement trajectory/count/size/movement speed/pressure], which helps with [writing/debugging/verification] of related scripts. + An error occurred diff --git a/settings.gradle.kts b/settings.gradle.kts index dd024d84..77a1ce21 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -202,6 +202,8 @@ pluginManagement { ?: compareVersionSuffix(ver1Suffix, ver2Suffix) } + fun compareVersionStringsDesc(v1: String, v2: String): Int = compareVersionStrings(v2, v1) + fun compareVersionParts(parts1: List, parts2: List): Int { for (i in 0 until maxOf(parts1.size, parts2.size)) { val part1 = parts1.getOrElse(i) { 0 } @@ -555,7 +557,7 @@ pluginManagement { val kotlin = object : Version( gradleKotlinCompatProps.filter { (gradleMin, _) -> gradle.gradleVersion.toGradleVersion() >= gradleMin.toGradleVersion() - }.toSortedMap(utils::compareVersionStrings).reversed(), + }.toSortedMap(utils::compareVersionStringsDesc), platform.version, ) { override fun refinedBestMatchingValue(bestMatchingValue: String?): String? { diff --git a/version.properties b/version.properties index d0a404ef..19fb6e06 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ -#Tue Jan 27 22:05:54 CST 2026 -BUILD_TIME=1769522754078 +#Wed Jan 28 15:47:25 CST 2026 +BUILD_TIME=1769586445990 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=3661 +VERSION_BUILD=3664 VERSION_NAME=6.7.0 Alpha18 VSCODE_EXT_REQUIRED_VERSION=1.0.13