Files
go-mobile-uiautomator/libs/selector.go
2026-03-10 15:49:36 +08:00

307 lines
8.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package libs
import (
"encoding/json"
"errors"
"fmt"
)
// FieldMeta 定义选择器字段的掩码位和默认值
// Bit 为该字段对应的掩码位Default 为默认值nil 表示无默认值)
type FieldMeta struct {
Bit uint32
Default interface{}
}
// Selector 表示一个 UiSelector 的构造器
// 用于构建 Android UI 元素的查询条件
type Selector struct {
// 存放已设置的字段及其值
fields map[string]interface{}
// 掩码值(通过设置/删除字段自动维护)
mask uint32
// 子/兄弟关系列表,元素为 "child" 或 "sibling"
childOrSibling []string
// 对应的嵌套 Selector 列表,长度与 childOrSibling 相同
childOrSiblingSelector []*Selector
}
// fieldDefs 定义所有支持的选择器字段及其元数据(与 Python 版 uiautomator2 一致)
var fieldDefs = map[string]FieldMeta{
"text": {Bit: 0x01, Default: nil},
"textContains": {Bit: 0x02, Default: nil},
"textMatches": {Bit: 0x04, Default: nil},
"textStartsWith": {Bit: 0x08, Default: nil},
"className": {Bit: 0x10, Default: nil},
"classNameMatches": {Bit: 0x20, Default: nil},
"description": {Bit: 0x40, Default: nil},
"descriptionContains": {Bit: 0x80, Default: nil},
"descriptionMatches": {Bit: 0x0100, Default: nil},
"descriptionStartsWith": {Bit: 0x0200, Default: nil},
"checkable": {Bit: 0x0400, Default: false},
"checked": {Bit: 0x0800, Default: false},
"clickable": {Bit: 0x1000, Default: false},
"longClickable": {Bit: 0x2000, Default: false},
"scrollable": {Bit: 0x4000, Default: false},
"enabled": {Bit: 0x8000, Default: false},
"focusable": {Bit: 0x010000, Default: false},
"focused": {Bit: 0x020000, Default: false},
"selected": {Bit: 0x040000, Default: false},
"packageName": {Bit: 0x080000, Default: nil},
"packageNameMatches": {Bit: 0x100000, Default: nil},
"resourceId": {Bit: 0x200000, Default: nil},
"resourceIdMatches": {Bit: 0x400000, Default: nil},
"index": {Bit: 0x800000, Default: 0},
"instance": {Bit: 0x01000000, Default: 0},
}
// New 创建一个新的 Selector可选传入初始字段
func New(initial map[string]interface{}) (*Selector, error) {
s := &Selector{
fields: make(map[string]interface{}),
childOrSibling: []string{},
childOrSiblingSelector: []*Selector{},
mask: 0,
}
for k, v := range initial {
if err := s.Set(k, v); err != nil {
return nil, err
}
}
return s, nil
}
// MustNew 与 New 相同,但在出错时 panic适合简洁的初始化场景
func MustNew(initial map[string]interface{}) *Selector {
s, err := New(initial)
if err != nil {
panic(err)
}
return s
}
// validateValue 对给定字段和值进行类型校验
// 布尔字段要求值为 bool 类型,整数字段要求值为整数类型
func validateValue(key string, val interface{}) error {
meta, ok := fieldDefs[key]
if !ok {
return fmt.Errorf("不支持的字段: %s", key)
}
if meta.Default == false {
// 布尔字段校验
_, ok := val.(bool)
if !ok {
return fmt.Errorf("%s 必须是 bool 类型", key)
}
return nil
}
// 整数字段校验
switch d := meta.Default.(type) {
case int:
switch val.(type) {
case int, int8, int16, int32, int64:
return nil
case uint, uint8, uint16, uint32, uint64:
return nil
default:
return fmt.Errorf("%s 必须是整数类型, 默认值=%v", key, d)
}
default:
// 其他字段无特殊类型要求
return nil
}
}
// Set 设置字段值并更新掩码
// 如果字段名非法或类型不匹配则返回错误
func (s *Selector) Set(key string, val interface{}) error {
if _, ok := fieldDefs[key]; !ok {
return fmt.Errorf("不支持的字段: %s", key)
}
if err := validateValue(key, val); err != nil {
return err
}
s.fields[key] = val
s.mask = s.mask | fieldDefs[key].Bit
return nil
}
// Delete 删除字段并更新掩码
// 删除不存在的字段不会报错(幂等操作)
func (s *Selector) Delete(key string) error {
if _, ok := fieldDefs[key]; !ok {
return fmt.Errorf("不支持的字段: %s", key)
}
if _, present := s.fields[key]; present {
delete(s.fields, key)
s.mask = s.mask & ^fieldDefs[key].Bit
}
return nil
}
// Mask 返回当前掩码值(只读)
func (s *Selector) Mask() uint32 {
return s.mask
}
// Child 添加一个子元素选择器
func (s *Selector) Child(initial map[string]interface{}) (*Selector, error) {
child, err := New(initial)
if err != nil {
return nil, err
}
s.childOrSibling = append(s.childOrSibling, "child")
s.childOrSiblingSelector = append(s.childOrSiblingSelector, child)
return s, nil
}
// Sibling 添加一个兄弟元素选择器
func (s *Selector) Sibling(initial map[string]interface{}) (*Selector, error) {
child, err := New(initial)
if err != nil {
return nil, err
}
s.childOrSibling = append(s.childOrSibling, "sibling")
s.childOrSiblingSelector = append(s.childOrSiblingSelector, child)
return s, nil
}
// UpdateInstance 更新最后一个子/兄弟选择器的 instance 字段
// 如果没有子/兄弟选择器,则更新根选择器的 instance
func (s *Selector) UpdateInstance(i int) error {
n := len(s.childOrSiblingSelector)
if n > 0 {
return s.childOrSiblingSelector[n-1].Set("instance", i)
}
return s.Set("instance", i)
}
// Clone 深拷贝当前 Selector包括所有子/兄弟选择器
func (s *Selector) Clone() *Selector {
clone := &Selector{
fields: make(map[string]interface{}, len(s.fields)),
mask: s.mask,
childOrSibling: append([]string{}, s.childOrSibling...),
childOrSiblingSelector: make([]*Selector, 0, len(s.childOrSiblingSelector)),
}
for k, v := range s.fields {
clone.fields[k] = v
}
for _, c := range s.childOrSiblingSelector {
clone.childOrSiblingSelector = append(clone.childOrSiblingSelector, c.Clone())
}
return clone
}
// ToMap 将选择器序列化为 map便于 JSON 编码或 RPC 调用
// 始终包含 childOrSibling 和 childOrSiblingSelector 字段(即使为空),
// 与 Python 版本保持一致,确保 UIAutomator2 服务端能正确解析
func (s *Selector) ToMap() map[string]interface{} {
out := make(map[string]interface{}, len(s.fields)+3)
for k, v := range s.fields {
out[k] = v
}
out["mask"] = s.mask
out["childOrSibling"] = append([]string{}, s.childOrSibling...)
cs := make([]map[string]interface{}, 0, len(s.childOrSiblingSelector))
for _, c := range s.childOrSiblingSelector {
cs = append(cs, c.ToMap())
}
out["childOrSiblingSelector"] = cs
return out
}
// ToJSON 返回选择器的 JSON 编码
func (s *Selector) ToJSON() ([]byte, error) {
return json.Marshal(s.ToMap())
}
// FromMap 从 map 恢复 Selector 实例
// 自动解析已知字段、掩码和子/兄弟选择器
func FromMap(data map[string]interface{}) (*Selector, error) {
root := &Selector{
fields: make(map[string]interface{}),
childOrSibling: []string{},
childOrSiblingSelector: []*Selector{},
mask: 0,
}
// 恢复已知字段
for k := range fieldDefs {
if v, ok := data[k]; ok {
if err := root.Set(k, v); err != nil {
return nil, err
}
}
}
// 恢复掩码值
if m, ok := data["mask"]; ok {
switch mv := m.(type) {
case float64:
root.mask = uint32(mv)
case uint32:
root.mask = mv
case int:
root.mask = uint32(mv)
case int64:
root.mask = uint32(mv)
}
}
// 恢复子/兄弟关系列表
if cs, ok := data["childOrSibling"]; ok {
if arr, ok := cs.([]interface{}); ok {
for _, e := range arr {
if sname, ok := e.(string); ok {
root.childOrSibling = append(root.childOrSibling, sname)
}
}
}
}
// 恢复子/兄弟选择器
if css, ok := data["childOrSiblingSelector"]; ok {
if arr, ok := css.([]interface{}); ok {
for _, item := range arr {
if m, ok := item.(map[string]interface{}); ok {
c, err := FromMap(m)
if err != nil {
return nil, err
}
root.childOrSiblingSelector = append(root.childOrSiblingSelector, c)
}
}
}
}
return root, nil
}
// UpdateAtPath 在指定路径上更新字段
// path 为逐级索引,例如 [0,2] 表示 childOrSiblingSelector[0].childOrSiblingSelector[2]
func (s *Selector) UpdateAtPath(path []int, updates map[string]interface{}) error {
node := s
for _, idx := range path {
if idx < 0 || idx >= len(node.childOrSiblingSelector) {
return errors.New("路径索引越界")
}
node = node.childOrSiblingSelector[idx]
}
for k, v := range updates {
if err := node.Set(k, v); err != nil {
return err
}
}
return nil
}
// String 实现 fmt.Stringer 接口,输出可读的选择器表示
func (s *Selector) String() string {
m := s.ToMap()
if _, ok := m["childOrSibling"]; !ok {
delete(m, "childOrSibling")
delete(m, "childOrSiblingSelector")
}
b, _ := json.Marshal(m)
return "Selector " + string(b)
}