使用Go语言解析多点触摸协议(MIT)TypeB由ADB调试产生的数据

前言

我们在使用安卓手机的时候,已经很少有人会不借助触摸操作来操作手机了,也就是说对手机的触摸操作已经是我们使用手机时最常见的方式了。那么我们究竟是如何使用这种方式操控手机的呢?手机又是如何处理这些信息的呢?

我们对于手机的触摸操作产生的信息,由Linux/Android多点触摸协议规定了如何进行组织和传输。而对于不同的手机设备,多点触摸协议又分为TypeA和TypeB两种,我的新手机(嘿嘿嘿开心)OnePlus7 pro是属于TypeB类型的,那么在这里就用TypeB来举例说明。

而选择Go语言来进行编写而不是使用诸如Python这种更加成熟,更加适合写脚本的语言来进行数据的解析和组织的原因是,我最近比较迷golang,仅此而已。而且使用Python现成的包我又有什么能学到呢?

概念解释

  • 多点触摸协议
    • TypeA
    • TypeB
  • ADB

多点触摸协议(Multi-touch Protocol)

为了满足现在的先进设备在面对多点和多用户时的数据采集,Linux实现了现在的多点触摸协议,多点触摸协议分为TypeA和TypeB两种,分别面对不同的硬件。TypeA是面对没有多用户的设备,通过所有的点的发送报告来跟踪不同的触点行为。而TypeB则是支持多用户的设备,通过使用不同的信号槽(slot)和信号轨道(track)来发送数据。


TypeA

举一个TypeA设备中多点触摸协议产生的数据的小例子(两个触摸点的最小发送序列):

   ABS_MT_POSITION_X x[0]
   ABS_MT_POSITION_Y y[0]
   SYN_MT_REPORT
   ABS_MT_POSITION_X x[1]
   ABS_MT_POSITION_Y y[1]
   SYN_MT_REPORT
   SYN_REPORT

可以很清晰的看出,多点触摸协议的内容是通过报告的形式发送的,即SYN_MT_REPORT和SYN_REPORT。

前者代表一个时刻中一个点发送了一个数据点的报告,也就是说,x[0]y[0]这个点的触摸记录和x[1]y[1]的触摸记录是同时被报告的,它们都通过一个SYN_REPORT被发送到上级应用或者协议。

在报告的时候,有时不仅仅包含坐标点的信息:触摸宽度和压力在某些设备上也会被记录。


TypeB

接下来是同样的只有两个触摸点的最小发送序列。

   ABS_MT_SLOT 0
   ABS_MT_TRACKING_ID 45
   ABS_MT_POSITION_X x[0]
   ABS_MT_POSITION_Y y[0]
   ABS_MT_SLOT 1
   ABS_MT_TRACKING_ID 46
   ABS_MT_POSITION_X x[1]
   ABS_MT_POSITION_Y y[1]
   SYN_REPORT

可以清晰的看出,每一个单独的触摸轨迹,在一个SYN_REPORT中通过一个信号槽发送。

信号槽是可以复用的,当一个slot中的tracking id为-1的时候,回收一个slot。


ADB - Android Debug Bridge

Android 调试桥 (adb) 是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试应用,并提供对 Unix shell(可用来在模拟器或连接的设备上运行各种命令)的访问。

可以很简单的在你的电脑上使用ADB和你的手机进行连接。

安装

sudo apt-get install adb

启动

adb shell

更多的使用方法读者可以自己去查找,这里不是专门讲解adb的文章,所以就不赘述了。

流程

使用Go语言解析多点触摸协议(MIT)TypeB由ADB调试产生的数据_第1张图片

数据结构

使用Go语言解析多点触摸协议(MIT)TypeB由ADB调试产生的数据_第2张图片

代码:

文件结构

.
├── analysis
│   ├── analysis.go
│   ├── dataStructure.go
│   ├── factory.go
│   └── out.txt
└── getevent
    ├── getevent.go
    └── out.txt

./getevent/getevent.go

package main

import (
    "bufio"
    "fmt"
    "os/exec"
    "os"
    "time"
)

func main() {
	start := time.Now()
    cmd := exec.Command("adb", "shell", "getevent", "-lt")

	file, err := os.OpenFile("out.txt", os.O_WRONLY|os.O_APPEND, 0644)
	defer file.Close()

    stdout, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Printf("Error:can not obtain stdout pipe for command:%s\n", err)
        return
    }
    if err := cmd.Start(); err != nil {
        fmt.Println("Error:The command is err,", err)
        return
    }
 
    outputBuf := bufio.NewReaderSize(stdout, 128)
    errChan := make(chan error)
    count := 0

    go func() {
    	for {
    		output, _, _ := outputBuf.ReadLine()
    		file.Write(append(output, '\n'))
    		go func() {
    			count += 1
    			fmt.Printf("%s Now: %d\r", fmtDuration(time.Since(start)), count)
    		}()
    	}
    }()

    var str string
    fmt.Scanln(&str)
    return

	errChan <-cmd.Wait()
    if <-errChan != nil {
  		return
 	}
 	return
}

func fmtDuration(d time.Duration) string {
    d = d.Round(time.Second)
    h := d / time.Hour
    d -= h * time.Hour
    m := d / time.Minute
    d -= m * time.Minute
    s := d / time.Second
    return fmt.Sprintf("%02d:%02d:%02d", h, m, s)
}

./analysis/analysis.go

package main

import (
	"fmt"
	"os"
	"bufio"
	"sync"
	"strings"
)

func main() {
	fmt.Println("[INFO]: Start Running...")

	file, err := os.Open("out.txt")
	if err != nil {
		fmt.Printf("[Error]: %s\n", err)
	}
	defer file.Close()

	store := Storage{
		Map: make(map[int]Event),
		Lock: new(sync.Mutex),
	}
	var wg sync.WaitGroup

	counter := 0
	scanner := bufio.NewScanner(file)
	
	for scanner.Scan() {
		counter += 1
		wg.Add(1)

		go func(id int, text string) {
			defer wg.Done()

			if strings.HasPrefix(text, "[") {
				newEvent := Factory(text)
				
				store.Lock.Lock()
				store.Map[id] = newEvent
				store.Lock.Unlock()
			}
		}(counter, scanner.Text())
	}

	wg.Wait()

// Now obey the MIT TypeB protocol...

	collection := Collection{
		Slots: make([]Slot, 10),
	}
	slotNow := 0
	collection.Slots[0] = Slot {
		Tracks: []Track{},
		Used: true,
	}
	var trackNow Track
	posNow := make([]Position, 10)
	widthNow := make([]int64, 10)
	
	trackCounter := 0
	pointCounter := 0
	maxSlot := 0
	
	for i:=1; i<=counter; i++ {
		eventNow := store.Map[i]

        if eventNow.Statu == "EV_ABS" {
        	if eventNow.Info == "ABS_MT_TRACKING_ID" {
        		if eventNow.Value == -1 {
        			if i<counter && store.Map[i+1].Statu == "EV_KEY" {
        				// the up action
        				trackNow.Reports = append(trackNow.Reports, Report {
        					Time: store.Map[i+1].Time,
        					IsBtn: true,
        				})

        				collection.Slots[slotNow].Tracks = append(collection.Slots[slotNow].Tracks, trackNow)
        				trackNow = Track{}
        				// fmt.Println("Track is done.")
        			}

        			collection.Slots[slotNow].Tracks = append(collection.Slots[slotNow].Tracks, trackNow)
        			// close the slot here
        			posNow[slotNow] = Position{}
        			widthNow[slotNow] = 0
        			trackCounter += 1
        		} else {
        			trackNow = Track{
        				ID: eventNow.Value,
        				Reports: []Report{},
        			}
        		}
        	} else if eventNow.Info == "ABS_MT_SLOT" {
        		// update the slot
        		if int64(slotNow) != eventNow.Value {
        			slotNow = int(eventNow.Value)
        		}

        		// init new slot
        		if !collection.Slots[slotNow].Used {
        			collection.Slots[slotNow] = Slot {
        				Used: true,
        				Tracks: []Track{},
        			}
        		}
        	} else if eventNow.Info == "ABS_MT_POSITION_X" {
        		posNow[slotNow].X = eventNow.Value
        	} else if eventNow.Info == "ABS_MT_POSITION_Y" {
        		posNow[slotNow].Y = eventNow.Value
        	} else if eventNow.Info == "ABS_MT_WIDTH_MAJOR" {
        		widthNow[slotNow] = eventNow.Value
        	}
        } else { // EV_SYN
        	if i > 1 && strings.HasPrefix(store.Map[i-1].Info, "ABS_MT_POSITION_") {
        		// report the position
        		trackNow.Reports = append(trackNow.Reports, Report {
        			Time: store.Map[i-1].Time,
        			IsBtn: false,
        			Pos: Position {
        				X: posNow[slotNow].X,
        				Y: posNow[slotNow].Y,
        			},
        			Width: widthNow[slotNow],
        		})
        		pointCounter += 1
        		// fmt.Println("New Position:", posNow[slotNow])
        	}
        }
	}

	for collection.Slots[maxSlot].Used {
		maxSlot += 1
	}

	fmt.Println("+------------------+--------------------+")
	fmt.Printf("| 总解析日志行数   | %10d         |\n", counter)
	fmt.Println("+------------------+--------------------+")
	fmt.Printf("| 总触摸经历时间   | %10d         |\n", int(store.Map[counter].Time-store.Map[1].Time)/1000)
	fmt.Println("+------------------+--------------------+")
	fmt.Printf("| 总捕捉触摸点数   | %10d         |\n", pointCounter)
	fmt.Println("+------------------+--------------------+")
	fmt.Printf("| 总触摸轨迹数量   | %10d         |\n", trackCounter)
	fmt.Println("+------------------+--------------------+")
	fmt.Printf("| 最大多点触摸数   | %10d         |\n", maxSlot)
	fmt.Println("+------------------+--------------------+")
	return
}

./analysis/dataStructure.go

package main

import "sync"

type Position struct {
	X        int64
	Y        int64
}

type Report struct {
	Time     float64
	IsBtn    bool
	Pos      Position
	Width    int64
}

type Track struct {
	ID       int64
	Reports  []Report
}

type Slot struct {
	Tracks   []Track
	Used     bool
}

type Collection struct {
	Slots    []Slot
}

type Event struct {
	Time     float64
	Statu    string
	Info     string
	Value    int64
}

type Storage struct {
	Map   map[int]Event
	Lock  *sync.Mutex
}

./analysis/factory.go

package main

import (
    "strings"
    "strconv"
)

func Factory(text string) Event {
	var segs []string
	pieces := strings.Split(text, " ")
	for _, piece := range pieces {
		if piece != "" {
			segs = append(segs, piece)	
		}
	}

	newEvent := Event {
		Statu: segs[3],
		Info: segs[4],
	}
	if segs[5] == "DOWN" {
		newEvent.Value = -333 // Down
	} else if segs[5] == "UP" {
		newEvent.Value = -444 // Up
	} else if segs[5] == "ffffffff" {
		newEvent.Value = -1 // -1, means that's track is done
	} else {
		num, err := strconv.ParseInt(segs[5], 16, 32)
		if err != nil {
			panic(err)
		}
		newEvent.Value = num
	}

	newEvent.Time, _ = strconv.ParseFloat(strings.TrimRight(segs[1], "]"), 64)
	return newEvent
}

你可能感兴趣的:(多点触摸协议,安卓,Linux,ADB,Golang,go,adb,多点触摸协议)