6.7.0 - Alpha18 - 主页抽屉增加 "指针位置" 工具; 浮动按钮 "更多" 对话框异步加载数据

This commit is contained in:
SuperMonster003
2026-01-28 16:02:28 +08:00
parent 22a6640963
commit 148d347ed5
26 changed files with 488 additions and 179 deletions

View File

@@ -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",

View File

@@ -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<View.OnClickListener> mOnClickListeners = new ArrayList<>();
private final ArrayList<Integer> mIcons = new ArrayList<>();
private final ArrayList<String> mTexts = new ArrayList<>();
private final ArrayList<String> mTitles = new ArrayList<>();
private final ArrayList<Supplier<String>> 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<Integer, String> 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<String> 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<String> 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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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<out Any?>): 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<out Any?>): Boolean = ensureArgumentsIsEmpty(args) {
!isPointerLocationEnabled(scriptRuntime, args)
PointerLocationTool.isPointerLocationDisabled(globalContext)
}
@JvmStatic

View File

@@ -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<Capture, Void, Void> 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;
}
}

View File

@@ -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,

View File

@@ -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()
}
}
}
}

View File

@@ -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)

View File

@@ -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)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,23 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="16dp"
android:paddingStart="24dp"
android:paddingEnd="16dp"
android:paddingTop="16dp">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="16dp"
android:paddingStart="24dp"
android:paddingEnd="16dp"
android:paddingTop="16dp">
<ImageView
android:id="@+id/icon"
android:layout_width="20dp"
android:layout_height="20dp" />
android:id="@+id/icon"
android:translationY="1dp"
android:layout_width="20dp"
android:layout_height="20dp"
tools:src="@drawable/ic_control_point_black_48dp" />
<TextView
android:id="@+id/text"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@@ -25,4 +33,19 @@
android:textColor="@color/day_night"
tools:text="@string/text_sample_string"
android:textSize="16sp" />
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="center_vertical"
android:textColor="@color/day_night"
android:textSize="12sp"
android:visibility="gone"
tools:visibility="visible"
tools:text="@string/text_sample_string" />
</LinearLayout>
</LinearLayout>

View File

@@ -1200,5 +1200,7 @@
<string name="dialog_button_homepage">الصفحة الرئيسية</string>
<string name="error_failed_to_change_the_toggle_state">تعذّر تغيير حالة المفتاح</string>
<string name="text_post_notifications_permission_rationale">لضمان عمل خدمات المقدّمة في AutoJs6 وغيرها بشكل طبيعي، وتمكين السكربتات من نشر الإشعارات، يجب منح AutoJs6 إذن \"نشر الإشعارات\".</string>
<string name="description_pointer_location">\"موقع المؤشر\" هي ميزة تصحيح أخطاء ضمن خيارات المطوّر في Android.\nعند تفعيلها، يعرض النظام على الشاشة معلومات نقاط اللمس مثل [الإحداثيات/مسار الحركة/العدد/الحجم/سرعة الحركة/الضغط]، مما يسهل [كتابة/تصحيح/التحقق من] السكربتات ذات الصلة.</string>
<string name="error_an_error_occurred">حدث خطأ</string>
</resources>

View File

@@ -1195,5 +1195,7 @@
<string name="dialog_button_homepage">Homepage</string>
<string name="error_failed_to_change_the_toggle_state">Failed to change the toggle state</string>
<string name="text_post_notifications_permission_rationale">To ensure that AutoJs6 foreground services, etc. can work properly and that scripts can post notifications, AutoJs6 must be granted the \"post notifications\" permission.</string>
<string name="description_pointer_location">\"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.</string>
<string name="error_an_error_occurred">An error occurred</string>
</resources>

View File

@@ -1198,5 +1198,7 @@
<string name="dialog_button_homepage">Inicio</string>
<string name="error_failed_to_change_the_toggle_state">No se pudo cambiar el estado del interruptor</string>
<string name="text_post_notifications_permission_rationale">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\".</string>
<string name="description_pointer_location">\"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.</string>
<string name="error_an_error_occurred">Se produjo un error</string>
</resources>

View File

@@ -1198,5 +1198,7 @@
<string name="dialog_button_homepage">Accueil</string>
<string name="error_failed_to_change_the_toggle_state">Échec de la modification de l\'état de l\'interrupteur</string>
<string name="text_post_notifications_permission_rationale">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\".</string>
<string name="description_pointer_location">\"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.</string>
<string name="error_an_error_occurred">Une erreur s\'est produite</string>
</resources>

View File

@@ -1199,5 +1199,7 @@
<string name="dialog_button_homepage">ホームページ</string>
<string name="error_failed_to_change_the_toggle_state">スイッチの状態の変更に失敗しました</string>
<string name="text_post_notifications_permission_rationale">AutoJs6 のフォアグラウンドサービス等が正常に動作し, スクリプトが通知を投稿できるようにするため, AutoJs6 には \"通知の送信\" 権限を付与する必要があります.</string>
<string name="description_pointer_location">\"ポインタの位置\" は Android の開発者向けオプションにあるデバッグ機能です.\n有効にすると, システムが画面上にタッチポイントの [座標/移動軌跡/数/サイズ/移動速度/圧力] などの情報を表示し, 関連スクリプトの [作成/デバッグ/検証] に役立ちます.</string>
<string name="error_an_error_occurred">エラーが発生しました</string>
</resources>

View File

@@ -1200,5 +1200,7 @@
<string name="dialog_button_homepage">홈페이지</string>
<string name="error_failed_to_change_the_toggle_state">스위치 상태 변경에 실패했습니다</string>
<string name="text_post_notifications_permission_rationale">AutoJs6 포그라운드 서비스 등이 정상적으로 동작하고 스크립트가 알림을 게시할 수 있도록 하려면, AutoJs6 에 \"알림 게시\" 권한을 부여해야 합니다.</string>
<string name="description_pointer_location">\"포인터 위치\" 는 Android 개발자 옵션에 있는 디버깅 기능입니다.\n활성화하면 시스템이 화면에 터치 지점의 [좌표/이동 궤적/개수/크기/이동 속도/압력] 등의 정보를 표시하여 관련 스크립트의 [작성/디버깅/검증] 에 도움이 됩니다.</string>
<string name="error_an_error_occurred">오류가 발생했습니다</string>
</resources>

View File

@@ -68,7 +68,7 @@
<string name="default_value_working_directory">/Сценарии</string>
<string name="description_about_app_and_developer_preference">AutoJs6 - это автоматизированный инструмент JavaScript для android, который является открытым исходным кодом и клонирован из \"hyb1996/Auto.js\".\n\nЧтобы попросить о помощи или обратной связи, посетите GitHub issues или присоединитесь к группе Tencent QQ 690946137.</string>
<string name="description_about_app_tips_preference">1. Нажмите и удерживайте значок приложения на странице \"О приложении и разработчике\", чтобы перейти на страницу параметров разработчика AutoJs6.</string>
<string name="description_accessibility_service">Служба доступности ключевая возможность AutoJs6 для автоматизации. Она используется для чтения информации об элементах интерфейса на экране и для имитации взаимодействий, таких как [ нажатие / свайп / ввод ].\n\nБольшинство функций, связанных с автоматизацией, а также инструменты вроде анализа макета, требуют включения службы доступности для корректной работы.</string>
<string name="description_accessibility_service">Служба доступности - ключевая возможность AutoJs6 для автоматизации. Она используется для чтения информации об элементах интерфейса на экране и для имитации взаимодействий, таких как [ нажатие / свайп / ввод ].\n\nБольшинство функций, связанных с автоматизацией, а также инструменты вроде анализа макета, требуют включения службы доступности для корректной работы.</string>
<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>
@@ -1198,5 +1198,7 @@
<string name="dialog_button_homepage">Главная</string>
<string name="error_failed_to_change_the_toggle_state">Не удалось изменить состояние переключателя</string>
<string name="text_post_notifications_permission_rationale">Чтобы обеспечить корректную работу фоновых служб переднего плана AutoJs6 и возможность публикации уведомлений скриптами, AutoJs6 необходимо предоставить разрешение \"публикации уведомлений\".</string>
<string name="description_pointer_location">\"Положение указателя\" - это отладочная функция в параметрах разработчика Android.\nПосле включения система будет отображать на экране сведения о точке(ах) касания, такие как [координаты/траектория движения/количество/размер/скорость движения/давление], что упрощает [написание/отладку/проверку] соответствующих скриптов.</string>
<string name="error_an_error_occurred">Произошла ошибка</string>
</resources>

View File

@@ -1196,5 +1196,7 @@
<string name="dialog_button_homepage">主頁</string>
<string name="error_failed_to_change_the_toggle_state">開關狀態改變失敗</string>
<string name="text_post_notifications_permission_rationale">為確保 AutoJs6 前台服務等能夠正常運行, 腳本能夠發佈通知, AutoJs6 需要被授予 \"發佈通知權限\".</string>
<string name="description_pointer_location">\"指針位置\" 是安卓開發者選項中的調試功能.\n開啓後, 系統會在屏幕上顯示觸摸點的 [座標/移動軌跡/數量/大小/移動速度/壓力] 等信息, 便於相關腳本的 [編寫/調試/校對] 等.</string>
<string name="error_an_error_occurred">發生錯誤</string>
</resources>

View File

@@ -1196,5 +1196,7 @@
<string name="dialog_button_homepage">主頁</string>
<string name="error_failed_to_change_the_toggle_state">開關狀態改變失敗</string>
<string name="text_post_notifications_permission_rationale">為確保 AutoJs6 前臺服務等能夠正常執行, 指令碼能夠釋出通知, AutoJs6 需要被授予 \"釋出通知許可權\".</string>
<string name="description_pointer_location">\"指標位置\" 是安卓開發者選項中的除錯功能.\n開啟後, 系統會在螢幕上顯示觸控點的 [座標/移動軌跡/數量/大小/移動速度/壓力] 等資訊, 便於相關指令碼的 [編寫/除錯/校對] 等.</string>
<string name="error_an_error_occurred">發生錯誤</string>
</resources>

View File

@@ -1196,5 +1196,7 @@
<string name="dialog_button_homepage">主页</string>
<string name="error_failed_to_change_the_toggle_state">开关状态改变失败</string>
<string name="text_post_notifications_permission_rationale">为确保 AutoJs6 前台服务等能够正常运行, 脚本能够发布通知, AutoJs6 需要被授予 \"发布通知权限\".</string>
<string name="description_pointer_location">\"指针位置\" 是安卓开发者选项中的调试功能.\n开启后, 系统会在屏幕上显示触摸点的 [坐标/移动轨迹/数量/大小/移动速度/压力] 等信息, 便于相关脚本的 [编写/调试/校对] 等.</string>
<string name="error_an_error_occurred">发生错误</string>
</resources>

View File

@@ -1455,5 +1455,7 @@
<string name="dialog_button_homepage">Homepage</string>
<string name="error_failed_to_change_the_toggle_state">Failed to change the toggle state</string>
<string name="text_post_notifications_permission_rationale">To ensure that AutoJs6 foreground services, etc. can work properly and that scripts can post notifications, AutoJs6 must be granted the \"post notifications\" permission.</string>
<string name="description_pointer_location">\"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.</string>
<string name="error_an_error_occurred">An error occurred</string>
</resources>

View File

@@ -202,6 +202,8 @@ pluginManagement {
?: compareVersionSuffix(ver1Suffix, ver2Suffix)
}
fun compareVersionStringsDesc(v1: String, v2: String): Int = compareVersionStrings(v2, v1)
fun compareVersionParts(parts1: List<Int>, parts2: List<Int>): 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? {

View File

@@ -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