6.7.0 - Alpha13 - 新增 auto.state 属性 (getter) 及 shizuku.state 属性 (getter)

This commit is contained in:
SuperMonster003
2026-01-08 16:32:00 +08:00
parent 1d89e40486
commit ea29d83a3f
13 changed files with 184 additions and 49 deletions

View File

@@ -1,7 +1,7 @@
{
"$data": {
"v6.7.0": {
"released_date": "2026/01/06",
"released_date": "2026/01/08",
"feature": [
"插件中心功能, 支持插件的安装/卸载/更新等操作 (入口: 主页抽屉按钮)",
"cvt 模块, 用于数据单位转换 (参阅 项目文档 > [单位转换](https://docs.autojs6.com/#/cvt))",
@@ -19,6 +19,7 @@
"http 模块请求相关方法支持缓存控制选项参数 (cacheBody/bodyCacheThresholdBytes)",
"http 模块请求相关方法支持不安全选项参数 (isInsecure/insecure), 用于忽略证书相关异常 _[`issue #417`](http://issues.autojs6.com/417)_",
"http 模块请求相关方法支持 options.client 选项, 用于配置 OkHttpClient.Builder (如 followRedirects 等) _[`issue #454`](http://issues.autojs6.com/454)_",
"auto.state 属性 (getter) 及 shizuku.state 属性 (getter), 用于获取无障碍服务状态及 Shizuku 服务状态",
"runtime.(set/is)JavaPrimitiveWrap 方法, 用于设置或获取 Java 原始类型包装策略 _[`issue #435`](http://issues.autojs6.com/435)_",
"autojs.(restart/exit) 方法, 用于重启或退出 AutoJs6 应用, 并支持应用重启时自动运行其参数指定的脚本 _[`issue #460`](http://issues.autojs6.com/460)_",
"UiObject#isShifted 方法, 用于检测控件位置变化 _[`issue #469`](http://issues.autojs6.com/469)_",

View File

@@ -22,7 +22,7 @@ public class AccessibilityBridgeImpl extends AccessibilityBridge {
@Override
public void ensureServiceStarted(boolean isForcibleRestart) {
if (isForcibleRestart && mA11yTool.serviceExists()) {
if (isForcibleRestart && mA11yTool.hasService()) {
mA11yTool.stopService(true);
Log.d(TAG, "isForcibleRestart");
}

View File

@@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantLock
/**
* Created by Stardust on May 2, 2017.
* Modified by SuperMonster003 as of Dec 29, 2025.
* Modified by SuperMonster003 as of Jan 7, 2026.
*/
open class AccessibilityService : android.accessibilityservice.AccessibilityService() {
@@ -87,6 +87,10 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
instance = this
val type = event.eventType
// Mark service as operational when we receive the first accessibility event.
// zh-CN: 当收到首个无障碍事件时, 将服务标记为可工作状态.
markOperationalStateIfNeeded()
// Snapshot callbacks to avoid holding lock while invoking user code.
// zh-CN: 为回调建立快照, 以避免在调用用户代码时持锁.
val callbacks: List<AccessibilityEventCallback> = synchronized(eventBoxLock) {
@@ -149,6 +153,8 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
instance = null
bridge = null
resetOperationalState()
mEventExecutor?.shutdownNow()
callback?.onDisconnected()
EventBus.getDefault().post(object : AccessibilityServiceStateChangedEvent {})
@@ -183,8 +189,13 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
private val LOCK = ReentrantLock()
private val ENABLED = LOCK.newCondition()
private val OPERATIONAL = LOCK.newCondition()
private var callback: AccessibilityServiceCallback? = null
@Volatile
var hasOperationalState = false
var instance: AccessibilityService? = null
private set
@@ -198,6 +209,27 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
fun hasInstance() = instance != null
private fun markOperationalStateIfNeeded() {
if (hasOperationalState) return
LOCK.lock()
try {
if (hasOperationalState) return
hasOperationalState = true
OPERATIONAL.signalAll()
} finally {
LOCK.unlock()
}
}
private fun resetOperationalState() {
LOCK.lock()
try {
hasOperationalState = false
} finally {
LOCK.unlock()
}
}
fun addDelegate(uniquePriority: Int, delegate: AccessibilityDelegate) {
// @Hint by 抠脚本人 (https://github.com/little-alei) on Jul 10, 2023.
// ! 用于记录 eventTypes 中的事件 id.
@@ -239,6 +271,30 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
}
}
// Wait until the service becomes operational (not only connected).
// zh-CN: 等待服务进入可工作状态 (不只是已连接).
fun waitForOperational(timeout: Long = DEFAULT_A11Y_SERVICE_START_TIMEOUT): Boolean {
if (hasInstance() && hasOperationalState) {
return true
}
LOCK.lock()
try {
if (hasInstance() && hasOperationalState) {
return true
}
if (timeout == -1L) {
OPERATIONAL.await()
return true
}
return OPERATIONAL.await(timeout, TimeUnit.MILLISECONDS)
} catch (e: InterruptedException) {
e.printStackTrace()
return false
} finally {
LOCK.unlock()
}
}
@JvmStatic
fun clearAccessibilityEventCallback() {
instance?.let { svc ->

View File

@@ -48,7 +48,7 @@ class AccessibilityTool(private val context: Context? = null) {
}
try {
mApplicationContext.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
} catch (e: ActivityNotFoundException) {
} catch (_: ActivityNotFoundException) {
ViewUtils.showToast(mContext, R.string.go_to_accessibility_settings, true)
}
}
@@ -60,20 +60,27 @@ class AccessibilityTool(private val context: Context? = null) {
else -> emptyArray()
}
@ScriptInterface
fun isServiceRunning() = hasInstance() && serviceExists()
@ScriptInterface
fun hasInstance() = AccessibilityService.hasInstance()
// Means the service is enabled in system settings.
// zh-CN: 表示服务在系统设置中处于已启用状态.
@ScriptInterface
fun serviceExists(): Boolean {
fun hasService(): Boolean {
val services = Secure.getString(
mApplicationContext.contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES
) ?: return false
return services.split(SERVICES_DELIMITER).any { it.trim() == mServiceName }
}
@ScriptInterface
fun isRunning() = hasService() && hasInstance()
// Means the service is enabled and confirmed workable in current process.
// zh-CN: 表示服务已启用, 且在当前进程中已确认可用.
@ScriptInterface
fun isOperational() = isRunning() && AccessibilityService.hasOperationalState
@JvmOverloads
@ScriptInterface
fun startService(withLaunchSettings: Boolean = true): Boolean {
@@ -81,7 +88,7 @@ class AccessibilityTool(private val context: Context? = null) {
if (startServiceWithConvenientWaysIfPossible()) {
result = true.also { Log.d(TAG, "Accessibility service enabled successfully by a certain \"convenient way\"") }
}
if (isServiceRunning()) {
if (isRunning()) {
result = true.also { Log.d(TAG, "Accessibility service is running") }
}
return result || false.also { if (withLaunchSettings) launchSettings() }
@@ -102,7 +109,7 @@ class AccessibilityTool(private val context: Context? = null) {
}
if (!result) {
if (AccessibilityService.stop() && !serviceExists()) {
if (AccessibilityService.stop() && !hasService()) {
result = true.also { Log.d(TAG, "Accessibility service disabled successfully by \"disableSelf\"") }
}
}
@@ -122,7 +129,7 @@ class AccessibilityTool(private val context: Context? = null) {
@JvmOverloads
@ScriptInterface
fun startServiceAndWaitFor(timeout: Long = DEFAULT_A11Y_SERVICE_START_TIMEOUT) {
if (isServiceRunning()) return
if (isRunning()) return
if (startServiceWithConvenientWaysIfPossibleAndWaitFor()) return
launchSettings()
if (!AccessibilityService.waitForStarted(timeout)) {
@@ -130,6 +137,23 @@ class AccessibilityTool(private val context: Context? = null) {
}
}
// Start service and wait until it becomes operational.
// zh-CN: 启动服务并等待其进入可工作状态.
@JvmOverloads
@ScriptInterface
fun startServiceAndWaitForOperational(timeout: Long = DEFAULT_A11Y_SERVICE_START_TIMEOUT) {
if (isOperational()) return
if (startServiceWithConvenientWaysIfPossibleAndWaitFor(timeout)) {
AccessibilityService.waitForOperational(timeout)
if (isOperational()) return
} else {
launchSettings()
}
if (!AccessibilityService.waitForOperational(timeout)) {
throw ScriptInterruptedException()
}
}
private fun startServiceWithConvenientWaysIfPossible() = when {
startServiceWithRootIfPossible() -> true
startServiceWithSecureIfPossible() -> true
@@ -144,12 +168,26 @@ class AccessibilityTool(private val context: Context? = null) {
@ScriptInterface
fun ensureService() {
if (isServiceRunning()) return
if (isRunning()) return
if (startServiceWithConvenientWaysIfPossibleAndWaitFor()) return
launchSettings()
if (AccessibilityService.waitForStarted()) return
when {
!serviceExists() -> throw ScriptException(mContext.getString(R.string.text_a11y_service_enabled_but_not_running))
!hasService() -> throw ScriptException(mContext.getString(R.string.text_a11y_service_enabled_but_not_running))
else -> throw ScriptException(mContext.getString(R.string.error_no_accessibility_permission))
}
}
// Ensure service is operational, otherwise throw a concrete error.
// zh-CN: 确保服务处于可工作状态, 否则抛出明确的错误信息.
@ScriptInterface
fun ensureServiceOperational(timeout: Long = DEFAULT_A11Y_SERVICE_START_TIMEOUT) {
if (isOperational()) return
startServiceAndWaitForOperational(timeout)
if (isOperational()) return
when {
!hasService() -> throw ScriptException(mContext.getString(R.string.text_a11y_service_enabled_but_not_running))
!hasInstance() -> throw ScriptException(mContext.getString(R.string.text_a11y_service_enabled_but_not_running))
else -> throw ScriptException(mContext.getString(R.string.error_no_accessibility_permission))
}
}
@@ -159,8 +197,8 @@ class AccessibilityTool(private val context: Context? = null) {
private fun isRootAccessible() = Pref.shouldStartA11yServiceWithRoot() && RootUtils.isRootAvailable()
private fun startServiceWithRoot(timeout: Long? = null): Boolean = when (timeout != null) {
true -> startServiceWithRoot() && AccessibilityService.waitForStarted(timeout.toLong())
else -> try {
true -> startServiceWithRoot() && AccessibilityService.waitForStarted(timeout)
else -> runCatching {
stopServiceWithRoot()
val services = getServicesWithRoot(true)
@@ -172,31 +210,27 @@ class AccessibilityTool(private val context: Context? = null) {
val resultState = ProcessShell.execCommand(cmdState, true)
TextUtils.isEmpty(resultServices.error) && TextUtils.isEmpty(resultState.error)
} catch (e: Exception) {
false
}
}.isSuccess
}
private fun startServiceWithRootIfPossible(timeout: Long? = null) = isRootAccessible() && startServiceWithRoot(timeout)
private fun startServiceWithSecure(timeout: Long? = null): Boolean = when (timeout != null) {
true -> startServiceWithSecure() && AccessibilityService.waitForStarted(timeout)
else -> try {
else -> runCatching {
stopServiceWithSecure()
val services = getServicesWithSecure(true)
Secure.putString(mApplicationContext.contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES, services)
Secure.putInt(mApplicationContext.contentResolver, Secure.ACCESSIBILITY_ENABLED, 1)
serviceExists()
} catch (e: Exception) {
false
}
hasService()
}.isSuccess
}
private fun startServiceWithSecureIfPossible(timeout: Long? = null) = isSecureAccessible() && startServiceWithSecure(timeout)
private fun stopServiceWithRoot() = try {
private fun stopServiceWithRoot() = runCatching {
val services = getServicesWithRoot(false)
val cmdServices = "settings put secure enabled_accessibility_services $services"
@@ -206,20 +240,16 @@ class AccessibilityTool(private val context: Context? = null) {
val resultState = ProcessShell.execCommand(cmdState, true)
TextUtils.isEmpty(resultServices.error) && TextUtils.isEmpty(resultState.error)
} catch (e: Exception) {
false
}
}.isSuccess
private fun stopServiceWithSecure() = try {
private fun stopServiceWithSecure() = runCatching {
val services = getServicesWithSecure(false)
Secure.putString(mApplicationContext.contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES, services)
Secure.putInt(mApplicationContext.contentResolver, Secure.ACCESSIBILITY_ENABLED, 0)
!serviceExists()
} catch (e: Exception) {
false
}
!hasService()
}.isSuccess
private fun getServicesWithRoot(withAutoJs: Boolean? = null): String = when (withAutoJs) {
true -> attachAutoJsService(getServicesWithRoot(false))

View File

@@ -75,7 +75,7 @@ public abstract class LayoutInspectTileService extends TileService implements La
/* Ignored. */
}
if (mA11yTool.isServiceRunning()) {
if (mA11yTool.isRunning()) {
mCapturing = true;
captureCurrentWindowDelayed();
} else {

View File

@@ -128,14 +128,17 @@ object WrappedShizuku {
fun isInstalled(context: Context) = getLaunchIntent(context) != null
@ScriptInterface
fun hasPermission() = try {
fun hasPermission() = runCatching {
Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
} catch (e: Throwable) {
false.also { if (e.message?.contains(Regex("binder .+n[o']t been received", RegexOption.IGNORE_CASE)) == false) e.printStackTrace() }
}.getOrElse { e ->
if (e.message?.contains(Regex("binder .+n[o']t been received", RegexOption.IGNORE_CASE)) == false) {
e.printStackTrace()
}
return@getOrElse false
}
@ScriptInterface
fun isOperational(): Boolean = isRunning() && hasPermission()
fun isOperational() = isRunning() && hasPermission()
@ScriptInterface
fun isRunning() = mHasBinder

View File

@@ -14,6 +14,7 @@ import org.autojs.autojs.extension.ArrayExtensions.toNativeArray
import org.autojs.autojs.extension.FlexibleArray
import org.autojs.autojs.extension.FlexibleArray.Companion.component1
import org.autojs.autojs.extension.FlexibleArray.Companion.component2
import org.autojs.autojs.extension.ScriptableExtensions.defineProp
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.api.augment.Augmentable
import org.autojs.autojs.runtime.api.augment.Invokable
@@ -22,6 +23,7 @@ import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException
import org.autojs.autojs.util.RhinoUtils.UNDEFINED
import org.autojs.autojs.util.RhinoUtils.callFunction
import org.autojs.autojs.util.RhinoUtils.newNativeArray
import org.autojs.autojs.util.RhinoUtils.newNativeObject
import org.mozilla.javascript.BaseFunction
import org.mozilla.javascript.Context
import org.mozilla.javascript.NativeArray
@@ -38,8 +40,11 @@ class Auto(private val scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime
::stop.name,
::enable.name,
::disable.name,
::isRunning.name,
::hasInstance.name,
::hasService.name,
::exists.name,
::isRunning.name,
::isOperational.name,
::stateListener.name,
::registerEvent.name,
::registerEvents.name,
@@ -75,6 +80,14 @@ class Auto(private val scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime
"windowRoots" to Supplier {
scriptRuntime.accessibilityBridge.windowRoots().map { UiObject.createRoot(it) }.toNativeArray()
},
"state" to Supplier {
newNativeObject().also { o ->
o.defineProp("hasInstance", accessibilityTool.hasInstance())
o.defineProp("hasService", accessibilityTool.hasService())
o.defineProp("isRunning", accessibilityTool.isRunning())
o.defineProp("isOperational", accessibilityTool.isOperational())
}
}
)
override fun invoke(vararg args: Any?): Any = ensureArgumentsAtMost(args, 2) {
@@ -139,14 +152,32 @@ class Auto(private val scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime
@JvmStatic
@RhinoRuntimeFunctionInterface
fun isRunning(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = ensureArgumentsIsEmpty(args) {
accessibilityTool.isServiceRunning()
fun hasInstance(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = ensureArgumentsIsEmpty(args) {
accessibilityTool.hasInstance()
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun hasService(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = ensureArgumentsIsEmpty(args) {
accessibilityTool.hasService()
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun exists(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = ensureArgumentsIsEmpty(args) {
accessibilityTool.serviceExists()
accessibilityTool.hasService()
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun isRunning(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = ensureArgumentsIsEmpty(args) {
accessibilityTool.isRunning()
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun isOperational(scriptRuntime: ScriptRuntime, args: Array<out Any?>) = ensureArgumentsIsEmpty(args) {
accessibilityTool.isOperational()
}
@JvmStatic

View File

@@ -1,6 +1,7 @@
package org.autojs.autojs.runtime.api.augment.shizuku
import org.autojs.autojs.annotation.RhinoRuntimeFunctionInterface
import org.autojs.autojs.extension.ScriptableExtensions.defineProp
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.api.AbstractShell
import org.autojs.autojs.runtime.api.WrappedShizuku
@@ -8,6 +9,8 @@ import org.autojs.autojs.runtime.api.augment.Augmentable
import org.autojs.autojs.runtime.api.augment.Invokable
import org.autojs.autojs.runtime.api.augment.app.App
import org.autojs.autojs.runtime.api.augment.shell.Shell
import org.autojs.autojs.util.RhinoUtils.newNativeObject
import java.util.function.Supplier
@Suppress("unused", "UNUSED_PARAMETER")
class Shizuku(private val scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), Invokable {
@@ -21,6 +24,17 @@ class Shizuku(private val scriptRuntime: ScriptRuntime) : Augmentable(scriptRunt
::currentComponent.name,
)
override val selfAssignmentGetters = listOf<Pair<String, Supplier<Any?>>>(
"state" to Supplier {
newNativeObject().also { o ->
o.defineProp("isInstalled", WrappedShizuku.isInstalled(globalContext))
o.defineProp("isRunning", WrappedShizuku.isRunning())
o.defineProp("hasPermission", WrappedShizuku.hasPermission())
o.defineProp("isOperational", WrappedShizuku.isOperational())
}
}
)
override fun invoke(vararg args: Any?): AbstractShell.Result = execCommand(scriptRuntime, args)
companion object {

View File

@@ -11,7 +11,7 @@ open class AccessibilityService(final override val context: Context) : ServiceIt
private val mA11yTool = AccessibilityTool(context)
override val isRunning
get() = mA11yTool.serviceExists() || mA11yTool.isServiceRunning()
get() = mA11yTool.hasService() || mA11yTool.isRunning()
override fun active(): Boolean {
return mA11yTool.restartService(true)
@@ -44,7 +44,7 @@ open class AccessibilityService(final override val context: Context) : ServiceIt
}
override fun onToggleSuccess() {
if (mA11yTool.serviceExists() && !mA11yTool.isServiceRunning()) {
if (mA11yTool.hasService() && !mA11yTool.isRunning()) {
ViewUtils.showToast(context, R.string.text_a11y_service_enabled_but_not_running, true)
}
super.onToggleSuccess()

View File

@@ -64,7 +64,7 @@ abstract class BaseActivity : AppCompatActivity() {
// @Dubious by SuperMonster003 on Oct 28, 2024.
// ! Is it property to start a11y service automatically here?
// ! zh-CN: 无障碍服务自启动放在这里是否合适?
AccessibilityTool(this).apply { if (!isServiceRunning()) startService(false) }
AccessibilityTool(this).apply { if (!isRunning()) startService(false) }
}
private fun setApplicationLocale(context: Context) {

View File

@@ -318,7 +318,7 @@ public class CircularMenu implements LayoutInspector.CaptureAvailableListener {
mLayoutInspectDialog.dismiss();
mLayoutInspectDialog = null;
}
if (!mA11yTool.isServiceRunning()) {
if (!mA11yTool.isRunning()) {
if (!mA11yTool.startService(false)) {
ViewUtils.showToast(mContext, mContext.getString(R.string.error_no_accessibility_permission_to_capture));
mA11yTool.launchSettings();

View File

@@ -127,7 +127,7 @@ open class DrawerFragment : Fragment() {
override fun refreshSubtitle(aimState: Boolean) {
val oldSubtitle = mAccessibilityServiceItem.subtitle
if (aimState) {
if (mA11yTool.serviceExists() && !mA11yTool.isServiceRunning()) {
if (mA11yTool.hasService() && !mA11yTool.isRunning()) {
mAccessibilityServiceItem.subtitle = context.getString(R.string.text_malfunctioning)
} else {
mAccessibilityServiceItem.subtitle = null

View File

@@ -1,5 +1,5 @@
#Tue Jan 06 21:47:58 CST 2026
BUILD_TIME=1767707278134
#Thu Jan 08 01:30:58 CST 2026
BUILD_TIME=1767807058123
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=3581
VERSION_BUILD=3583
VERSION_NAME=6.7.0 Alpha13
VSCODE_EXT_REQUIRED_VERSION=1.0.8