Android 开放配件协议 (AOAv2) HID 开发指北

本文的所有代码示例都是基于 Go 语言的

引言

由于近期有对手机自动化的业务需求,也为了增强自己的 USB 开发能力,我学习了 Scrcpy (v1.25) 的源码,借用了 Scrcpy 服务器以实现手机屏幕的网络串流。

但在控制这一块,犯了难。

Scrcpy 的做法是,将捕获的鼠标和键盘事件通过 Socket 连接传送到 Scrcpy 服务器,由服务器利用 Android Shell 比较高的权限注入控制事件。

这种做法存在弊端:对于部分正在收紧 USB 调试 权限的设备来说,比如小米手机 你背叛了发烧友 ,需要开启更加细分的 USB 调试(安全设置) 选项,在这个过程中需要给手机联网、登录小米账号、反复输入小米账号密码三次。对于不联网的自动化测试机来说,这是不能接受的。

通过查阅,我注意到 Scrcpy 提供了 OTG MODE 选项。这个模式不需要启用 USB 调试 ,就算是在权限收紧后的小米手机上也可用。在激活 OTG MODE 之后,手机上直接显示了一个实体键盘的图标。

这激发了我浓厚的兴趣:这是怎么做到的?一番查阅,我看到了这个博客,文章介绍了一种 HID over AOAv2 的方案,本质就是利用谷歌提供的 Android 开放配件协议 (AOA) 实现 USB 连接设备的主从机转换,从而使电脑 (主机) 能够为 Android (从机) 注册 HID 设备并发送 HID 事件。

一番折腾和踩坑过后,我搞定并使用 Go 重新实现了 OTG MODE ,现在可以在这里写一些内容记录一下了。

这里要特别感谢

  • AlynxZhou 编写的博客和对 Scrcpy 的贡献
  • rom1v 开源的 Scrcpy 项目和分享的技术细节

前置工作

基础知识

要再现本文的技术,你至少需要:

  • Go 语言基础
  • CGO 编译环境
  • Linux 操作基础
  • 能根据协议标准进行报文的组织和发送

要深入理解本文的内容,你还需要理解:

环境配置

要运行本文的代码,你需要使用原生 Linux 系统 (虚拟机是不可行的) ,最好使用某个发行版 (例如 Ubuntu) 以防止出现内核不完整的问题。

在 Linux 系统中,你需要安装如下环境:

  • Go v1.20+
  • gousb
  • libusb-1.0.0-dev
  • gcc 编译套件

环境配置教程不在本文范围内,这里提供一个十全大补丸以供参考:

sudo apt-get install build-essential
sudo apt-get install pkg-config
sudo apt-get install libusb-1.0.0-dev
wget https://go.dev/dl/go1.20.4.linux-amd64.tar.gz
sudo rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.4.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go get -u github.com/google/gousb

正文

第一阶段: 获取 USB 设备

首先我们要获取全部的已连接 usb 设备,再筛选出其中的 Android 手机。

这一步没什么好的办法,只能通过 USB-IF 组织提供的厂商 Vendor ID 列表来匹配查询,这里举例筛选小米手机,则 Vendor ID 是 0x2717

func GetDevice() *gousb.Device {
devices, err := gousb.NewContext().OpenDevices(func(desc *gousb.DeviceDesc) bool {
       if desc.Vendor == 0x2717 {
           return true
      }
return false
})
if err != nil || len(devices) == 0 {
panic(err)
}
return devices[0]
}

当然,据说也可以通过 Android Device Serial 来匹配 USB 设备,但我没有深究,业务上同时只会连接一台手机。

获取到 USB 设备后我们可以打印它的厂商验证一下:

func main() {
   dev := GetDevice()
   defer dev.Close()
   manu, _ := dev.Manufacturer()
   fmt.Println(manu) // Xiaomi
}

可以发现打印出的厂商名字正是 Xiaomi ,这就是我们要的那台设备了。

第二阶段: 验证 Protocol 版本

Android 开放配件协议 (AOA) 有两个版本,一个是 v1 ,一个是 v2 。

只有 v2 版本的协议才能支持我们要实现的 HID 设备注册,所以验证设备的 Protocol 是很重要的。

而验证 Protocol ,其实就是根据 AOA 协议发送命令并接受返回数据,校验一下,代码如下:

func getProtocol(dev *gousb.Device) (uint16, error) {
if dev == nil {
return 0, errors.New("ErrorNoAccessoryDevice")
}
var data = make([]byte, 2)
   RTypeIN := gousb.ControlIn | gousb.ControlVendor
   GetProtocol := 51
n, err := dev.Control(RTypeIN, GetProtocol, 0, 0, data)
if err != nil {
return 0, err
}
if n != 2 {
return 0, errors.New("ErrorFailedToGetProtocol")
}
return (uint16(data[1])<<8 | uint16(data[0])), nil
}

我们将获取到的小米手机设备传入并检验一下,函数返回值为 2 ,说明支持的正是 AOAv2 协议。

这里要说明一下, AOAv2 协议是向下兼容 AOAv1 协议的,如果你希望实现其它的功能,请自己阅读上面提供的官方文档。本文只讲述如何实现 HID 设备。

待续

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情