Merge branch 'little-alei_master'

# Conflicts:
#	version.properties
This commit is contained in:
SuperMonster003
2023-07-12 17:22:50 +08:00
30 changed files with 521 additions and 224 deletions

View File

@@ -61,6 +61,23 @@ module.exports = function (scriptRuntime, scope) {
return util.java.toJsArray(a11yBridge.windowRoots(), false)
.map(root => UiObject.createRoot(root));
},
stateListener(listener) {
return a11yBridge.setAccessibilityListener(listener);
},
registerEvent(name, listener) {
return rtAutomator.registerEvent(name, listener);
},
/** @deprecated */
registerEvents(name, listener) {
return rtAutomator.registerEvent(name, listener);
},
removeEvent(name) {
return rtAutomator.removeEvent(name);
},
/** @deprecated */
removeEvents(name) {
return rtAutomator.removeEvent(name);
},
waitFor(timeout) {
automator.waitForService(timeout);
},

View File

@@ -70,7 +70,7 @@ public class ReadOnlyUiObject extends UiObject {
@Override
public String id() {
return mNodeInfo.getSimpleId();
return mNodeInfo.getId();
}
@Override

View File

@@ -150,4 +150,7 @@ public abstract class AccessibilityBridge {
return mConfig;
}
public void setAccessibilityListener(AccessibilityServiceCallback listener) {
AccessibilityService.Companion.setCallback(listener);
}
}

View File

@@ -8,33 +8,57 @@ import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED
import android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
import android.view.accessibility.AccessibilityNodeInfo
import org.autojs.autojs.core.accessibility.SimpleActionAutomator.Companion.AccessibilityEventCallback
import org.autojs.autojs.core.automator.AccessibilityEventWrapper
import org.autojs.autojs.event.EventDispatcher
import org.autojs.autojs.pref.Pref
import java.util.TreeMap
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
interface AccessibilityServiceCallback {
fun onConnected()
fun onDisconnected()
}
/**
* Created by Stardust on 2017/5/2.
*/
open class AccessibilityService : android.accessibilityservice.AccessibilityService() {
val onKeyObserver = OnKeyListener.Observer()
val keyInterrupterObserver = KeyInterceptor.Observer()
var fastRootInActiveWindow: AccessibilityNodeInfo? = null
var bridge: AccessibilityBridge? = null
private val eventBox = TreeMap<Int, AccessibilityEventCallback>()
private val gestureEventDispatcher = EventDispatcher<GestureListener>()
private val eventExecutor by lazy { Executors.newSingleThreadExecutor() }
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (instance != this) {
instance = this
private fun eventNameToType(str: String): Int {
return try {
val sb = StringBuilder()
sb.append("TYPE_")
val upperCase = str.uppercase(Locale.getDefault())
sb.append(upperCase)
AccessibilityEvent::class.java.getField(sb.toString()).get(null) as Int
} catch (unused: NoSuchFieldException) {
throw IllegalArgumentException("unknown event type: $str")
}
}
fun addAccessibilityEventCallback(name: String, callback: AccessibilityEventCallback) {
eventBox[eventNameToType(name)] = callback
}
fun removeAccessibilityEventCallback(name: String) {
eventBox.remove(eventNameToType(name))
}
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (instance != this) instance = this
val type = event.eventType
eventBox[type]?.onAccessibilityEvent(AccessibilityEventWrapper(event))
if (containsAllEventTypes || eventTypes.contains(type)) {
if (type == TYPE_WINDOW_STATE_CHANGED || type == TYPE_VIEW_FOCUSED) {
rootInActiveWindow?.also { fastRootInActiveWindow = it }
@@ -86,12 +110,12 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
instance = null
bridge = null
eventExecutor.shutdownNow()
callback?.onDisconnected()
super.onDestroy()
}
override fun onServiceConnected() {
instance = this
serviceInfo = serviceInfo.apply {
AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS.let {
flags = (if (Pref.isStableModeEnabled) flags and it.inv() else flags or it)
@@ -102,7 +126,7 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
}
}
}
callback?.onConnected()
super.onServiceConnected()
LOCK.lock()
@@ -122,6 +146,7 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
private val LOCK = ReentrantLock()
private val ENABLED = LOCK.newCondition()
private var callback: AccessibilityServiceCallback? = null
var instance: AccessibilityService? = null
private set
@@ -141,6 +166,7 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
fun isNotRunning() = !isRunning()
fun addDelegate(uniquePriority: Int, delegate: AccessibilityDelegate) {
// 用于记录eventTypes中的事件id
delegates[uniquePriority] = delegate
val set = delegate.eventTypes
if (set == null) {
@@ -178,13 +204,16 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
LOCK.unlock()
}
}
interface GestureListener {
fun onGesture(gestureId: Int)
@JvmStatic
fun clearAccessibilityEventCallback() {
instance?.eventBox?.clear()
}
fun setCallback(listener: AccessibilityServiceCallback) {
callback = listener
}
interface GestureListener {
fun onGesture(gestureId: Int)
}
}
}

View File

@@ -61,8 +61,7 @@ class LayoutInspector(private val mContext: Context) {
}
private fun refreshChildList(root: AccessibilityNodeInfo?) {
if (root == null)
return
if (root == null) return
root.refresh()
val childCount = root.childCount
for (i in 0 until childCount) {

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.core.accessibility
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
@@ -17,7 +18,7 @@ import java.util.HashMap
* Modified by SuperMonster003 as of Jun 17, 2022.
*/
@Suppress("unused")
@Suppress("unused", "MemberVisibilityCanBePrivate")
@Keep
class NodeInfo(private val resources: Resources?, private val node: UiObject, var parent: NodeInfo?) {
@@ -27,8 +28,8 @@ class NodeInfo(private val resources: Resources?, private val node: UiObject, va
val bounds = boundsToString(boundsInScreen)
val children = ArrayList<NodeInfo>()
var fullId: String? = node.viewIdResourceName
val simpleId = node.simpleId()
val fullId = node.fullId()
val id = node.simpleId()
val desc = node.desc()
val text = node.text()
val className = node.className()
@@ -59,6 +60,7 @@ class NodeInfo(private val resources: Resources?, private val node: UiObject, va
val childCount = node.childCount()
val actionNames = node.actionNames()
@SuppressLint("DiscouragedApi")
val idHex = takeIf { resources != null && packageName != null && fullId != null }?.let {
"0x${Integer.toHexString(resources!!.getIdentifier(fullId, null, null))}"
}
@@ -68,7 +70,7 @@ class NodeInfo(private val resources: Resources?, private val node: UiObject, va
"childCount=${children.size}, " +
"boundsInScreen=$boundsInScreen, " +
"boundsInParent=$boundsInParent, " +
"id='$simpleId', " +
"id='$id', " +
"desc='$desc', " +
"packageName='$packageName', " +
"text='$text', " +

View File

@@ -2,7 +2,6 @@
package org.autojs.autojs.core.accessibility
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.graphics.Bitmap
import android.graphics.Rect
@@ -15,7 +14,7 @@ import android.view.accessibility.AccessibilityNodeInfo
import androidx.annotation.RequiresApi
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import org.autojs.autojs.annotation.ScriptInterface
import org.autojs.autojs.core.accessibility.AccessibilityService.Companion.isRunning
import org.autojs.autojs.core.automator.AccessibilityEventWrapper
import org.autojs.autojs.core.automator.GlobalActionAutomator
import org.autojs.autojs.core.automator.UiObject
import org.autojs.autojs.core.automator.action.ActionFactory
@@ -27,7 +26,8 @@ import org.autojs.autojs.runtime.accessibility.AccessibilityConfig
import org.autojs.autojs.runtime.api.ScreenMetrics
import org.autojs.autojs.runtime.api.ScriptPromiseAdapter
import org.autojs.autojs.util.DeveloperUtils
import java.util.concurrent.atomic.AtomicInteger
import android.accessibilityservice.AccessibilityService as AndroidAccessibilityService
/**
* Created by Stardust on 2017/4/2.
@@ -177,11 +177,23 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge
fun paste(target: ActionTarget) = performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE))
@ScriptInterface
fun isServiceRunning() = isRunning()
fun isServiceRunning() = AccessibilityService.isRunning()
@ScriptInterface
fun ensureService() = accessibilityBridge.ensureServiceEnabled()
//todo:优化实现方式
// TODO by SuperMonster003 on Jul 12, 2023.
// ! Ref to Auto.js Pro
fun registerEvent(eventName: String, callback: AccessibilityEventCallback) {
ensureService()
AccessibilityService.instance?.addAccessibilityEventCallback(eventName, callback)
}
fun removeEvent(eventName: String) {
AccessibilityService.instance?.removeAccessibilityEventCallback(eventName)
}
private fun performAction(simpleAction: SimpleAction): Boolean {
ensureService()
if (AccessibilityConfig.isUnintendedGuardEnabled() && isRunningPackageSelf) {
@@ -204,8 +216,8 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge
val promiseAdapter = mPromiseAdapter ?: ScriptPromiseAdapter().also { mPromiseAdapter = it }
val service = accessibilityBridge.service!!
val executor = service.mainExecutor
val callback = object : AccessibilityService.TakeScreenshotCallback {
override fun onSuccess(screenshot: AccessibilityService.ScreenshotResult) {
val callback = object : AndroidAccessibilityService.TakeScreenshotCallback {
override fun onSuccess(screenshot: AndroidAccessibilityService.ScreenshotResult) {
val hardwareBuffer = Bitmap.wrapHardwareBuffer(screenshot.hardwareBuffer, screenshot.colorSpace)
// @Hint by SuperMonster003 on Jun 9, 2023.
@@ -222,7 +234,7 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge
}
override fun onFailure(errorCode: Int) {
if (errorCode == AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT) {
if (errorCode == AndroidAccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT) {
Handler(Looper.getMainLooper()).postDelayed({
captureScreen()
}, 50)
@@ -239,9 +251,12 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge
}
companion object {
val accessibilityDelegateCounter = AtomicInteger(1000)
val TAG: String = SimpleActionAutomator::class.java.name
interface AccessibilityEventCallback {
fun onAccessibilityEvent(event: AccessibilityEventWrapper)
}
}
}

View File

@@ -0,0 +1,38 @@
package org.autojs.autojs.core.automator
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
/**
* Created by 抠脚本人 on Jul 10, 2023.
*/
class AccessibilityEventWrapper(event: AccessibilityEvent) {
val raw = event
val packageName: CharSequence? = event.packageName
val eventType = event.eventType
val eventTime = event.eventTime
val action = event.action
val isFullScreen = event.isFullScreen
val className = event.className
val source = event.source?.let { UiObject(it, getDepth(it), getIndexInParent(it)) }
private fun getDepth(node: AccessibilityNodeInfo): Int {
var depth = 0
var father = node.parent
while (father != null) {
depth++
father = father.parent
}
return depth
}
private fun getIndexInParent(node: AccessibilityNodeInfo): Int {
var index = 0
val parent = node.parent ?: return 0
while (parent.getChild(index) != node) {
index++
}
return index
}
}

View File

@@ -31,11 +31,21 @@ import org.opencv.core.Size
*/
@Suppress("unused", "DEPRECATION")
open class UiObject constructor(info: Any?, private val allocator: AccessibilityNodeInfoAllocator?, private val depth: Int, private val indexInParent: Int) : AccessibilityNodeInfoCompat(info), UiObjectActions {
open class UiObject(
info: Any?,
private val allocator: AccessibilityNodeInfoAllocator?,
private val depth: Int,
private val indexInParent: Int
) : AccessibilityNodeInfoCompat(info), UiObjectActions {
private val bounds by lazy { AccessibilityNodeInfoHelper.getBoundsInScreen(this) }
constructor(info: Any?, allocator: AccessibilityNodeInfoAllocator, indexInParent: Int) : this(info, allocator, 0, indexInParent)
constructor(info: Any?, allocator: AccessibilityNodeInfoAllocator, indexInParent: Int) : this(
info,
allocator,
0,
indexInParent
)
@JvmOverloads
constructor(info: Any?, depth: Int = 0, indexInParent: Int = -1) : this(info, null, depth, indexInParent)
@@ -50,16 +60,35 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
}
} catch (e: IllegalStateException) {
// FIXME: 2017/5/5
null
null.also { e.printStackTrace() }
}
open fun child(i: Int): UiObject? = try {
super.getChild(i)?.run { UiObject(unwrap(), depth + 1, i) }
} catch (e: IllegalStateException) {
// FIXME: 2017/5/5
null
null.also { e.printStackTrace() }
}
@Deprecated("Deprecated in Java", ReplaceWith("offset(i)"))
open fun brother(i: Int): UiObject? = offset(i)
open fun offset(i: Int): UiObject? = try {
parent()?.child(indexInParent + i)
} catch (e: ArrayIndexOutOfBoundsException) {
null.also { e.printStackTrace() }
}
open fun sibling(i: Int): UiObject? = try {
parent()?.child(i)
} catch (e: ArrayIndexOutOfBoundsException) {
null.also { e.printStackTrace() }
}
open fun nextSibling() = sibling(1)
open fun previousSibling() = sibling(-1)
open fun childCount() = childCount
fun hasChildren() = childCount > 0
@@ -332,28 +361,29 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
}
} catch (e: IllegalStateException) {
// FIXME: 2017/5/5
false
false.also { e.printStackTrace() }
}
override fun performAction(action: Int): Boolean = try {
super.performAction(action)
} catch (e: IllegalStateException) {
// FIXME: 2017/5/5
false
false.also { e.printStackTrace() }
}
override fun getChild(index: Int): AccessibilityNodeInfoCompat = allocator?.getChild(this, index) ?: super.getChild(index)
override fun getChild(index: Int): AccessibilityNodeInfoCompat =
allocator?.getChild(this, index) ?: super.getChild(index)
override fun getParent(): AccessibilityNodeInfoCompat = allocator?.getParent(this) ?: super.getParent()
override fun findAccessibilityNodeInfosByText(text: String): List<AccessibilityNodeInfoCompat> {
return allocator?.findAccessibilityNodeInfosByText(this, text)
?: super.findAccessibilityNodeInfosByText(text)
?: super.findAccessibilityNodeInfosByText(text)
}
override fun findAccessibilityNodeInfosByViewId(viewId: String): List<AccessibilityNodeInfoCompat> {
return allocator?.findAccessibilityNodeInfosByViewId(this, viewId)
?: super.findAccessibilityNodeInfosByViewId(viewId)
?: super.findAccessibilityNodeInfosByViewId(viewId)
}
override fun recycle() {
@@ -375,7 +405,14 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
private val cArray = Array::class.java
private val RESULT_GROUP_WIDGET by lazy { arrayOf("#", "w", RESULT_TYPE_WIDGET) }
private val RESULT_GROUP_WIDGET_COLLECTION by lazy { arrayOf("{}", "wc", "collection", "list").plus(listAliases("#", "w")) }
private val RESULT_GROUP_WIDGET_COLLECTION by lazy {
arrayOf("{}", "wc", "collection", "list").plus(
listAliases(
"#",
"w"
)
)
}
private val RESULT_GROUP_WIDGETS by lazy { arrayOf("[]", "ws", "widgets").plus(arrayAliases("#", "w")) }
private val RESULT_GROUP_CONTENT by lazy { arrayOf("$", "txt", "content") }
private val RESULT_GROUP_CONTENTS by lazy { arrayOf("contents").plus(arrayAliases("$", "txt", "content")) }
@@ -386,7 +423,8 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
@JvmStatic
fun isCompass(s: Any?): Boolean {
return (s as? CharSequence)?.run { this == COMPASS_PASS_ON || isEmpty() || contains("^(([pkc>]|s[<>]?)-?\\d*)+$".toRegex()) } ?: false
return (s as? CharSequence)?.run { this == COMPASS_PASS_ON || isEmpty() || contains("^(([pkc>]|s[<>]?)-?\\d*)+$".toRegex()) }
?: false
}
@JvmStatic
@@ -417,13 +455,18 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
@JvmStatic
fun createRoot(root: AccessibilityNodeInfo?) = UiObject(root, null, 0, -1)
internal fun createRoot(root: AccessibilityNodeInfo?, allocator: AccessibilityNodeInfoAllocator?) = UiObject(root, allocator, 0, -1)
internal fun createRoot(root: AccessibilityNodeInfo?, allocator: AccessibilityNodeInfoAllocator?) =
UiObject(root, allocator, 0, -1)
private fun arrayAliases(symbol: String, vararg others: String): Array<String> {
return arrayOf("$symbol$symbol", "$symbol[]", "[$symbol]").plus(others.map { listOf("$it[]", "[$it]") }.flatten())
return arrayOf("$symbol$symbol", "$symbol[]", "[$symbol]").plus(others.map { listOf("$it[]", "[$it]") }
.flatten())
}
private fun listAliases(@Suppress("SameParameterValue") symbol: String, vararg others: String): Array<String> {
private fun listAliases(
@Suppress("SameParameterValue") symbol: String,
@Suppress("SameParameterValue") vararg others: String
): Array<String> {
return arrayOf("$symbol{}", "{$symbol}").plus(others.map { listOf("$it{}", "{$it}") }.flatten())
}
@@ -478,7 +521,12 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
}
}
internal class Detector(private val compass: CharSequence? = COMPASS_PASS_ON, private val result: Result, val callback: BaseFunction? = null, private val selector: UiSelector? = UiSelector()) {
internal class Detector(
private val compass: CharSequence? = COMPASS_PASS_ON,
private val result: Result,
val callback: BaseFunction? = null,
private val selector: UiSelector? = UiSelector()
) {
fun detect(): Any? = when (val type = result.type.let { if (it is List<*>) it[0] else it }.toString()) {
in RESULT_GROUP_WIDGET -> getUiObject()
@@ -524,13 +572,17 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
private fun getUiObject() = result.byOne()?.compass(compass)
private fun getUiObjectCollection(transformer: (UiObject?) -> UiObject?) = UiObjectCollection.transform(result.byAll(), transformer).let { RhinoUtils.wrap(it) }
private fun getUiObjectCollection(transformer: (UiObject?) -> UiObject?) =
UiObjectCollection.transform(result.byAll(), transformer).let { RhinoUtils.wrap(it) }
private fun getUiObjectArray(transformer: (UiObject?) -> Any?) = UiObjectCollection.mapNotNull(result.byAll(), transformer).let { RhinoUtils.toArray(it) }
private fun getUiObjectArray(transformer: (UiObject?) -> Any?) =
UiObjectCollection.mapNotNull(result.byAll(), transformer).let { RhinoUtils.toArray(it) }
private fun detectWithCallback(callback: BaseFunction?, args: Array<*>): Any? = callback?.let { RhinoUtils.callFunction(it, args) }
private fun detectWithCallback(callback: BaseFunction?, args: Array<*>): Any? =
callback?.let { RhinoUtils.callFunction(it, args) }
private fun detectWithCallback(callback: BaseFunction?, arg: Any?): Any? = callback?.let { detectWithCallback(it, arrayOf(arg)) }
private fun detectWithCallback(callback: BaseFunction?, arg: Any?): Any? =
callback?.let { detectWithCallback(it, arrayOf(arg)) }
}
@@ -596,15 +648,45 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
"setLiveRegion", "setMaxTextLength", "setMovementGranularities",
),
arrayOf(cString) to arrayOf(
"compass", "findAccessibilityNodeInfosByText", "findAccessibilityNodeInfosByViewId", "setClassName", "setContentDescription",
"setError", "setHintText", "setPackageName", "setPaneTitle", "setRoleDescription", "setStateDescription", "setText",
"setTooltipText", "setViewIdResourceName",
"compass",
"findAccessibilityNodeInfosByText",
"findAccessibilityNodeInfosByViewId",
"setClassName",
"setContentDescription",
"setError",
"setHintText",
"setPackageName",
"setPaneTitle",
"setRoleDescription",
"setStateDescription",
"setText",
"setTooltipText",
"setViewIdResourceName",
),
arrayOf(cBoolean) to arrayOf(
"setAccessibilityFocused", "setCanOpenPopup", "setCheckable", "setChecked", "setClickable", "setEditable", "setEnabled",
"setFocusable", "setFocused", "setHeading", "setImportantForAccessibility", "setLongClickable", "setMultiLine", "setPassword",
"setScreenReaderFocusable", "setScrollable", "setSelected", "setShowingHintText", "setTextEntryKey", "setVisibleToUser",
"setContentInvalid", "setContextClickable", "setDismissable",
"setAccessibilityFocused",
"setCanOpenPopup",
"setCheckable",
"setChecked",
"setClickable",
"setEditable",
"setEnabled",
"setFocusable",
"setFocused",
"setHeading",
"setImportantForAccessibility",
"setLongClickable",
"setMultiLine",
"setPassword",
"setScreenReaderFocusable",
"setScrollable",
"setSelected",
"setShowingHintText",
"setTextEntryKey",
"setVisibleToUser",
"setContentInvalid",
"setContextClickable",
"setDismissable",
),
arrayOf(cFloatPrim) to arrayOf("setProgress"),
arrayOf(cIntPrim, cIntPrim) to arrayOf("setTextSelection", "setSelection", "scrollTo"),
@@ -629,7 +711,11 @@ open class UiObject constructor(info: Any?, private val allocator: Accessibility
constructor(resultType: String, resultParams: Array<Any?>?) : super(
when (resultParams) {
null -> str(R.string.error_unknown_picker_result_type, resultType)
else -> str(R.string.error_unknown_picker_result_type_with_params, resultType, "[${resultParams.joinToString { it.toString() }}]")
else -> str(
R.string.error_unknown_picker_result_type_with_params,
resultType,
"[${resultParams.joinToString { it.toString() }}]"
)
}
)

View File

@@ -1,31 +0,0 @@
package org.autojs.autojs.core.ui;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import org.autojs.autojs.core.ui.inflater.util.Ids;
/**
* Created by Stardust on 2017/5/14.
*/
public class JsViewHelper {
@Nullable
public static View findViewByStringId(View view, String id) {
View result = view.findViewById(Ids.parse(id));
if (result != null)
return result;
if (!(view instanceof ViewGroup group)) {
return null;
}
for (int i = 0; i < group.getChildCount(); i++) {
result = findViewByStringId(group.getChildAt(i), id);
if (result != null)
return result;
}
return null;
}
}

View File

@@ -0,0 +1,22 @@
package org.autojs.autojs.core.ui
import android.view.View
import android.view.ViewGroup
import org.autojs.autojs.core.ui.inflater.util.Ids
/**
* Created by Stardust on 2017/5/14.
* Transformed by 抠脚本人 on Jul 10, 2023.
*/
object JsViewHelper {
@JvmStatic
fun findViewByStringId(view: View, id: String?): View? {
view.findViewById<View>(Ids.parse(id))?.let { return it }
if (view is ViewGroup) {
for (i in 0 until view.childCount) {
findViewByStringId(view.getChildAt(i), id)?.let { return it }
}
}
return null
}
}

View File

@@ -1,43 +0,0 @@
package org.autojs.autojs.model.autocomplete;
/**
* Created by Stardust on 2018/2/3.
*/
public class CodeCompletion {
private final String mHint;
private final String mUrl;
private final String mInsertText;
private final int mInsertPos;
public CodeCompletion(String hint, String url, int insertPos) {
mHint = hint;
mUrl = url;
mInsertPos = insertPos;
mInsertText = null;
}
public CodeCompletion(String hint, String url, String insertText) {
mHint = hint;
mUrl = url;
mInsertText = insertText;
mInsertPos = -1;
}
public String getHint() {
return mHint;
}
public String getUrl() {
return mUrl;
}
public String getInsertText() {
if (mInsertText != null)
return mInsertText;
if (mInsertPos == 0) {
return mHint;
}
return mHint.substring(mInsertPos);
}
}

View File

@@ -0,0 +1,34 @@
package org.autojs.autojs.model.autocomplete
/**
* Created by Stardust on 2018/2/3.
* Transformed by 抠脚本人 on Jul 11, 2023.
*/
class CodeCompletion {
val hint: String
val url: String?
private val mInsertText: String?
private val mInsertPos: Int
constructor(hint: String, url: String?, insertPos: Int) {
this.hint = hint
this.url = url
mInsertPos = insertPos
mInsertText = null
}
constructor(hint: String, url: String, insertText: String?) {
this.hint = hint
this.url = url
mInsertText = insertText
mInsertPos = -1
}
val insertText: String
get() {
if (mInsertText != null) return mInsertText
return if (mInsertPos == 0) {
hint
} else hint.substring(mInsertPos)
}
}

View File

@@ -1,48 +0,0 @@
package org.autojs.autojs.model.autocomplete;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/9/27.
*/
public class CodeCompletions {
private final int mFrom;
private final List<CodeCompletion> mCompletions;
public CodeCompletions(int cursor, List<CodeCompletion> completions) {
mFrom = cursor;
mCompletions = completions;
}
public static CodeCompletions just(List<String> hints) {
List<CodeCompletion> completions = new ArrayList<>(hints.size());
for (String hint : hints) {
completions.add(new CodeCompletion(hint, null, 0));
}
return new CodeCompletions(-1, completions);
}
public int getFrom() {
return mFrom;
}
public int size() {
return mCompletions.size();
}
public String getHint(int position) {
return mCompletions.get(position).getHint();
}
public CodeCompletion get(int pos) {
return mCompletions.get(pos);
}
public String getUrl(int pos) {
return mCompletions.get(pos).getUrl();
}
}

View File

@@ -0,0 +1,34 @@
package org.autojs.autojs.model.autocomplete
/**
* Created by Stardust on 2017/9/27.
* Transformed by 抠脚本人 on Jul 11, 2023.
*/
class CodeCompletions(val from: Int, private val mCompletions: List<CodeCompletion>) {
fun size(): Int {
return mCompletions.size
}
fun getHint(position: Int): String {
return mCompletions[position].hint
}
operator fun get(pos: Int): CodeCompletion {
return mCompletions[pos]
}
fun getUrl(pos: Int): String? {
return mCompletions[pos].url
}
companion object {
@JvmStatic
fun just(hints: List<String?>): CodeCompletions {
val completions: MutableList<CodeCompletion> = ArrayList(hints.size)
for (hint in hints) {
completions.add(CodeCompletion(hint!!, null, 0))
}
return CodeCompletions(-1, completions)
}
}
}

View File

@@ -9,10 +9,10 @@ import java.util.Arrays;
public class Symbols {
private static final CodeCompletions sSymbols = CodeCompletions.just(Arrays.asList(
",", ".", "=", ";", "\"", "'", "-", "_",
",", ".", "=", ";", "\"", "'", "/", "-", "_",
"(", ")", "[", "]", "{", "}", "<", ">",
"+", "*", "?", "$", "#", "@", "`",
"/", "\\", "&", "|", "!", "%", "×", "÷",
"+", "*", "?", ":", "$", "#", "@", "`",
"\\", "&", "|", "!", "%", "×", "÷",
"", "", "", "", "", "", "¥", "",
"°", "", "", "", "±", "", "",
"α", "β", "γ", "λ", "μ", "π", "σ", "ω",

View File

@@ -11,6 +11,7 @@ import org.autojs.autojs.AutoJs;
import org.autojs.autojs.annotation.ScriptVariable;
import org.autojs.autojs.concurrent.VolatileDispose;
import org.autojs.autojs.core.accessibility.AccessibilityBridge;
import org.autojs.autojs.core.accessibility.AccessibilityService;
import org.autojs.autojs.core.accessibility.SimpleActionAutomator;
import org.autojs.autojs.core.accessibility.UiSelector;
import org.autojs.autojs.core.activity.ActivityInfoProvider;
@@ -528,6 +529,9 @@ public class ScriptRuntime {
}
});
// 清空无障碍事件
ignoresException(AccessibilityService::clearAccessibilityEventCallback);
ignoresException(RootUtils::resetRuntimeOverriddenRootModeState);
ignoresException(ImageWrapper::recycleAll);

View File

@@ -285,7 +285,16 @@ open class EditorView : FrameLayout, OnHintClickListener, ClickCallback, Toolbar
.setFunctionsView(mFunctionsKeyboard)
.setEditView(editor!!.codeEditText)
.build()
//todo不清楚作用暂时注释掉
// @Hint by SuperMonster003 on Jul 12, 2023.
// ! 此处的点击事件回调注册是为了使功能键盘智能提示的属性可以实现其接口对应的功能:
// ! 点击: 自动补全, 并根据情况添加括号或句点符号等.
// ! 长按: 以浮动窗口形式展示 [方法/属性/模块] 对应的文档内容 (如果存在的话).
mFunctionsKeyboard!!.setClickCallback(this)
mShowFunctionsButton!!.setOnLongClickListener {
editor!!.beautifyCode()
true
}
}
private fun setUpInputMethodEnhancedBar() {
@@ -321,7 +330,7 @@ open class EditorView : FrameLayout, OnHintClickListener, ClickCallback, Toolbar
mAutoCompletion!!.onCursorChange(line, cursor)
}
fun setTheme(theme: Theme?) {
private fun setTheme(theme: Theme?) {
theme?.let {
mEditorTheme = it
editor!!.setTheme(it)
@@ -575,11 +584,19 @@ open class EditorView : FrameLayout, OnHintClickListener, ClickCallback, Toolbar
override fun onHintClick(completions: CodeCompletions, pos: Int) {
val completion = completions[pos]
editor!!.insert(completion.insertText)
//todo:增加行注释
if (completion.insertText=="/") {
editor!!.commentLine()
} else editor!!.insert(completion.insertText)
}
override fun onHintLongClick(completions: CodeCompletions, pos: Int) {
val completion = completions[pos]
//todo:增加块注释
if (completion.insertText=="/") {
editor!!.commentBlock()
return
}
if (completion.url == null) return
showManual(completion.url, completion.hint)
}

View File

@@ -403,11 +403,14 @@ public class CodeEditor extends HVScrollView {
public void beautifyCode() {
setProgress(true);
int pos = mCodeEditText.getSelectionStart();
mJsBeautifier.beautify(mCodeEditText.getText().toString(), new JsBeautifier.Callback() {
@Override
public void onSuccess(String beautifiedCode) {
setProgress(false);
mCodeEditText.setText(beautifiedCode);
// 格式化后恢复光标位置
mCodeEditText.setSelection(pos);
}
@Override
@@ -418,6 +421,43 @@ public class CodeEditor extends HVScrollView {
});
}
public void commentLine() {
//如果没有选中,则添加文本/,否则选中的行前加//
String selectionText = getSelectionRaw();
if (selectionText.equals("")) {
insert("/");
} else {
String[] lines = selectionText.split("\\n");
StringBuilder commentedText = new StringBuilder();
//处理取消注释
if (lines[0].startsWith("//")) {
for (String line : lines) {
commentedText.append(line.substring(2)).append("\n");
}
} else {
for (String line : lines) {
commentedText.append("//").append(line).append("\n");
}
}
mReplacement = commentedText.toString().replaceAll("\\n$", "");
replaceSelection();
}
}
public void commentBlock() {
String selectionText = getSelectionRaw();
if (!selectionText.isEmpty()) {
String regex = "/\\*([^*]|\\*+[^*/])*\\*/";
if (selectionText.matches(regex)) {
// 取消块注释
mReplacement = selectionText.substring(2, selectionText.length() - 2);
} else {
// 增加块注释
mReplacement = "/*" + selectionText + "*/";
}
replaceSelection();
}
}
public void insert(String insertText) {
int selection = Math.max(mCodeEditText.getSelectionStart(), 0);
@@ -437,13 +477,17 @@ public class CodeEditor extends HVScrollView {
return mCodeEditText.getText().toString();
}
public Observable<String> getSelection() {
public String getSelectionRaw() {
int s = mCodeEditText.getSelectionStart();
int e = mCodeEditText.getSelectionEnd();
if (s == e) {
return Observable.just("");
return "";
}
return Observable.just(mCodeEditText.getText().toString().substring(s, e));
return mCodeEditText.getText().toString().substring(s, e);
}
public Observable<String> getSelection() {
return Observable.just(getSelectionRaw());
}

View File

@@ -13,9 +13,11 @@ import org.autojs.autojs.ui.floating.layoutinspector.LayoutBoundsFloatyWindow
import org.autojs.autojs.ui.floating.layoutinspector.LayoutHierarchyFloatyWindow
import org.autojs.autojs.ui.floating.layoutinspector.NodeInfoView
import org.autojs.autojs.ui.widget.BubblePopupMenu
import org.autojs.autojs.util.ClipboardUtils
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs6.R
abstract class LayoutFloatyWindow(private val rootNode: NodeInfo?, private val context: Context, private val isServiceRelied: Boolean) : FullScreenFloatyWindow() {
abstract class LayoutFloatyWindow(private val rootNode: NodeInfo?, private val context: Context, private val isServiceRelied: Boolean) : FullScreenFloatyWindow(){
private lateinit var mServiceContext: Context
private lateinit var mActions: LinkedHashMap<Int, Runnable>
@@ -27,6 +29,12 @@ abstract class LayoutFloatyWindow(private val rootNode: NodeInfo?, private val c
private val mNodeInfoDialog by lazy {
AppLevelThemeDialogBuilder(mServiceContext)
.customView(mNodeInfoView, false)
.positiveText("生成")
.onPositive { _, _ ->
ViewUtils.showToast(context, "TODO")
val selector = mNodeInfoView.getCheckedDate().joinToString(".")
if (selector.isNotEmpty()) ClipboardUtils.setClip(context, selector)
}
.build()
.also { it.window!!.setType(FloatyWindowManger.getWindowType()) }
}

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.ui.floating.layoutinspector
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
@@ -97,7 +98,7 @@ open class LayoutBoundsView : View {
draw(canvas, child)
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (mRootNode != null) {
setSelectedNode(findNodeAt(mRootNode!!, event.rawX.toInt(), event.rawY.toInt()))

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.ui.floating.layoutinspector;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -18,12 +19,7 @@ import org.autojs.autojs.ui.widget.LevelBeamView;
import org.autojs.autojs.util.ViewUtils;
import org.autojs.autojs6.R;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.Stack;
import java.util.*;
import pl.openrnd.multilevellistview.ItemInfo;
import pl.openrnd.multilevellistview.MultiLevelListAdapter;
@@ -40,7 +36,7 @@ public class LayoutHierarchyView extends MultiLevelListView {
public interface OnItemLongClickListener {
void onItemLongClick(View view, NodeInfo nodeInfo);
}
private final Map<NodeInfo,ViewHolder> nodeMap = new LinkedHashMap<>();
private Adapter mAdapter;
private OnItemLongClickListener mOnItemLongClickListener;
private final AdapterView.OnItemLongClickListener mOnItemLongClickListenerProxy = new AdapterView.OnItemLongClickListener() {
@@ -111,17 +107,41 @@ public class LayoutHierarchyView extends MultiLevelListView {
}
private void setClickedItem(View view, NodeInfo item) {
mClickedNodeInfo = item;
if (mClickedView == null) {
mOriginalBackground = view.getBackground();
} else {
mClickedView.setBackground(mOriginalBackground);
drawListItem(mClickedNodeInfo, false);
}
view.setBackgroundColor(mClickedColor);
drawListItem(item, true);
mClickedNodeInfo = item;
mClickedView = view;
invalidate();
}
private void drawListItem(NodeInfo info, Boolean draw) {
ArrayList<ViewHolder> list = new ArrayList<>();
NodeInfo currentInfo = info;
while (true) {
currentInfo = currentInfo.getParent();
if (currentInfo == null) break;
ViewHolder vh = nodeMap.get(currentInfo);
list.add(vh);
}
//todo选用能适应深色模式的字体颜色
//fixme列表滑动时listview数据错乱
if (draw) {
for (ViewHolder vh : list) {
vh.nameView.setTextColor(Color.RED); // 设置字体颜色为红色
}
} else {
for (ViewHolder vh : list) {
vh.nameView.setTextColor(Color.BLACK); // 设置字体颜色为红色
}
}
}
private void initPaint() {
mPaint = new Paint();
mPaint.setColor(Color.DKGRAY);
@@ -186,7 +206,7 @@ public class LayoutHierarchyView extends MultiLevelListView {
return found;
}
private class ViewHolder {
private static class ViewHolder {
TextView nameView;
TextView infoView;
ImageView arrowView;
@@ -220,6 +240,7 @@ public class LayoutHierarchyView extends MultiLevelListView {
return mInitiallyExpandedNodes.contains((NodeInfo) object);
}
@SuppressLint("InflateParams")
@Override
public View getViewForObject(Object object, View convertView, ItemInfo itemInfo) {
NodeInfo nodeInfo = (NodeInfo) object;
@@ -231,8 +252,9 @@ public class LayoutHierarchyView extends MultiLevelListView {
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.nameView.setText(simplifyClassName(nodeInfo.getClassName()));
nodeMap.put(nodeInfo,viewHolder);
//对于id,desc,text,clickable,longClickable不为空的显示额外信息
viewHolder.nameView.setText(extraInfo(nodeInfo));
viewHolder.nodeInfo = nodeInfo;
if (viewHolder.infoView.getVisibility() == VISIBLE)
viewHolder.infoView.setText(getItemInfoDsc(itemInfo));
@@ -266,6 +288,31 @@ public class LayoutHierarchyView extends MultiLevelListView {
return s;
}
private String extraInfo(NodeInfo nodeInfo) {
String extra = simplifyClassName(nodeInfo.getClassName());
ArrayList<String> info = new ArrayList<>();
if (nodeInfo.getId() != null) {
info.add("id=" + nodeInfo.getId());
}
if (!nodeInfo.getText().equals("")) {
info.add("text=" + nodeInfo.getText());
}
if (nodeInfo.getDesc() != null) {
info.add("desc=" + nodeInfo.getDesc());
}
if (nodeInfo.getClickable()) {
info.add("clickable");
}
if (nodeInfo.getLongClickable()) {
info.add("longClickable");
}
//字符串拼接
String others = String.join(", ", info);
if (!others.isEmpty()) {
return extra + " [" + others + "]";
}
return extra;
}
private String getItemInfoDsc(ItemInfo itemInfo) {
StringBuilder builder = new StringBuilder();

View File

@@ -1,10 +1,12 @@
package org.autojs.autojs.ui.floating.layoutinspector
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -20,7 +22,7 @@ import java.lang.reflect.Field
* Modified by SuperMonster003 as of Dec 1, 2021.
*/
class NodeInfoView : RecyclerView {
//todo:调整数据结构,对话框关闭后根据已勾选的属性,生成选择器
private val data = Array(FIELDS.size + 1) { Array(2) { "" } }
constructor(context: Context) : super(context)
@@ -41,6 +43,7 @@ class NodeInfoView : RecyclerView {
)
}
@SuppressLint("NotifyDataSetChanged")
fun setNodeInfo(nodeInfo: NodeInfo) {
for (i in FIELDS.indices) {
try {
@@ -71,6 +74,14 @@ class NodeInfoView : RecyclerView {
}
}
fun getCheckedDate(): Array<String> {
//todo:数据增加checked属性区分已选中项目
val checkedArr = data.filter { it[0] == "id" || it[0] == "text" }
return Array(checkedArr.size) {
dataToFx(checkedArr[it])
}
}
private inner class Adapter : RecyclerView.Adapter<ViewHolder>() {
val mViewTypeHeader = 0
@@ -84,6 +95,7 @@ class NodeInfoView : RecyclerView {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.apply {
data[position].let {
//attrChecked.isChecked = false
attrName.text = it[0]
attrValue.text = it[1]
}
@@ -97,7 +109,7 @@ class NodeInfoView : RecyclerView {
}
internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//val attrChecked: CheckBox = itemView.findViewById(R.id.generate)
val attrName: TextView = itemView.findViewById(R.id.name)
val attrValue: TextView = itemView.findViewById(R.id.value)
@@ -130,7 +142,7 @@ class NodeInfoView : RecyclerView {
private val FIELD_NAMES = arrayOf(
// Common
"packageName", "simpleId", "fullId", "idHex",
"packageName", "id", "fullId", "idHex",
"desc", "text",
"bounds", "className",
"clickable", "longClickable", "scrollable",

View File

@@ -1,12 +0,0 @@
package org.autojs.autojs.ui.floating.layoutinspector;
import org.autojs.autojs.core.accessibility.NodeInfo;
/**
* Created by Stardust on 2017/3/10.
*/
public interface OnNodeInfoSelectListener {
void onNodeSelect(NodeInfo info);
}

View File

@@ -0,0 +1,10 @@
package org.autojs.autojs.ui.floating.layoutinspector
import org.autojs.autojs.core.accessibility.NodeInfo
/**
* Created by Stardust on 2017/3/10.
*/
fun interface OnNodeInfoSelectListener {
fun onNodeSelect(info: NodeInfo)
}

View File

@@ -63,15 +63,16 @@
android:layout_alignParentBottom="true" />
<ImageView
android:id="@+id/functions"
android:layout_width="40dp"
android:layout_height="35dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:background="?selectableItemBackgroundBorderless"
android:padding="6dp"
android:src="@drawable/ic_ali_fx"
app:tint="#222329" />
android:id="@+id/functions"
android:layout_width="40dp"
android:layout_height="35dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/fun"
android:padding="6dp"
android:src="@drawable/ic_ali_fx"
app:tint="#222329" />
<org.autojs.autojs.ui.edit.completion.CodeCompletionBar
android:id="@+id/code_completion_bar"

View File

@@ -10,6 +10,11 @@
android:orientation="horizontal"
android:padding="8dp">
<CheckBox
android:id="@+id/generate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="TouchTargetSizeCheck" />
<TextView
android:id="@+id/name"
android:layout_width="0dp"

View File

@@ -880,6 +880,7 @@
<string name="error_colon_must_follow_a_valid_ip_address">Colon must follow a valid IP address</string>
<string name="error_repeated_dot_symbol">Repeated dot symbol</string>
<string name="error_repeated_colon_symbol">Repeated colon symbol</string>
<string name="fun">函数</string>
<plurals name="text_items_total_sum">
<item quantity="one">Total: %d item</item>