如何使用 AccessibilityService 在 Android 上执行拖动(基于 X、Y 鼠标坐标)?

Posted

技术标签:

【中文标题】如何使用 AccessibilityService 在 Android 上执行拖动(基于 X、Y 鼠标坐标)?【英文标题】:How perform a drag (based in X,Y mouse coordinates) on Android using AccessibilityService? 【发布时间】:2020-04-04 06:44:01 【问题描述】:

我想知道如何基于 X、Y 鼠标坐标在 android 上执行拖动?考虑两个简单的例子,Team Viewer/QuickSupport 分别在远程智能手机上绘制“密码模式”和 Windows Paint 的 Pen。

我能做的只有simulate touch(dispatchGesture()AccessibilityNodeInfo.ACTION_CLICK)。

我找到了这些相关链接,但不知道它们是否有用:

Perform swipe on screen using AccessibilityService Example 1 Continued gestures

下面是我的工作代码,用于将鼠标坐标(在PictureBox 控件内)发送到远程手机并模拟触摸。

Windows 窗体应用程序:

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)

    foreach (ListViewItem item in lvConnections.SelectedItems)
    
        // Remote screen resolution
        string[] tokens = item.SubItems[5].Text.Split('x'); // Ex: 1080x1920

        int xClick = (e.X * int.Parse(tokens[0].ToString())) / (pictureBox1.Size.Width);
        int yClick = (e.Y * int.Parse(tokens[1].ToString())) / (pictureBox1.Size.Height);

        Client client = (Client)item.Tag;

        if (e.Button == MouseButtons.Left)
            client.sock.Send(Encoding.UTF8.GetBytes("TOUCH" + xClick + "<|>" + yClick + Environment.NewLine));
    


编辑:

我的最后一次尝试是分别使用鼠标坐标(C# Windows 窗体应用程序)和自定义 android 例程(参考上面链接的“滑动屏幕”代码)的“滑动屏幕”:

private Point mdownPoint = new Point();

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)

    foreach (ListViewItem item in lvConnections.SelectedItems)
    
        // Remote screen resolution
        string[] tokens = item.SubItems[5].Text.Split('x'); // Ex: 1080x1920

        Client client = (Client)item.Tag;

        if (e.Button == MouseButtons.Left)
        
            xClick = (e.X * int.Parse(tokens[0].ToString())) / (pictureBox1.Size.Width); 
            yClick = (e.Y * int.Parse(tokens[1].ToString())) / (pictureBox1.Size.Height);

            // Saving start position:

            mdownPoint.X = xClick; 
            mdownPoint.Y = yClick; 

            client.sock.Send(Encoding.UTF8.GetBytes("TOUCH" + xClick + "<|>" + yClick + Environment.NewLine));
        
    


private void PictureBox1_MouseMove(object sender, MouseEventArgs e)

    foreach (ListViewItem item in lvConnections.SelectedItems)
    
        // Remote screen resolution
        string[] tokens = item.SubItems[5].Text.Split('x'); // Ex: 1080x1920

        Client client = (Client)item.Tag;

        if (e.Button == MouseButtons.Left)
        
            xClick = (e.X * int.Parse(tokens[0].ToString())) / (pictureBox1.Size.Width);
            yClick = (e.Y * int.Parse(tokens[1].ToString())) / (pictureBox1.Size.Height);

            client.sock.Send(Encoding.UTF8.GetBytes("MOUSESWIPESCREEN" + mdownPoint.X + "<|>" + mdownPoint.Y + "<|>" + xClick + "<|>" + yClick + Environment.NewLine));
        
    

android AccessibilityService

public void Swipe(int x1, int y1, int x2, int y2, int time) 

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) 
    System.out.println(" ======= Swipe =======");

    GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
    Path path = new Path();
    path.moveTo(x1, y1);
    path.lineTo(x2, y2);

    gestureBuilder.addStroke(new GestureDescription.StrokeDescription(path, 100, time));
    dispatchGesture(gestureBuilder.build(), new GestureResultCallback() 
        @Override
        public void onCompleted(GestureDescription gestureDescription) 
            System.out.println("SWIPE Gesture Completed :D");
            super.onCompleted(gestureDescription);
        
    , null);



产生以下结果(但仍然不能像 TeamViewer 那样绘制“模式密码”)。但就像下面评论中所说的那样,我认为使用类似的方法可以使用Continued gestures 来实现。欢迎在这个方向提出任何建议。


编辑 2:

当然,解决方案是continued gestures,就像之前的编辑所说的那样。

Simulating joystick movement using AccessibilityService Why the continueStroke function is not work

下面是我发现的假定固定代码here =>

android AccessibilityService:

// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down.
Path path = new Path();
path.moveTo(200,200);
path.lineTo(400,200);

final GestureDescription.StrokeDescription sd = new GestureDescription.StrokeDescription(path, 0, 500, true);

// The starting point of the second path must match
// the ending point of the first path.
Path path2 = new Path();
path2.moveTo(400,200);
path2.lineTo(400,400);

final GestureDescription.StrokeDescription sd2 = sd.continueStroke(path2, 0, 500, false); // 0.5 second

HongBaoService.mService.dispatchGesture(new GestureDescription.Builder().addStroke(sd).build(), new AccessibilityService.GestureResultCallback()

@Override
public void onCompleted(GestureDescription gestureDescription)
super.onCompleted(gestureDescription);
HongBaoService.mService.dispatchGesture(new GestureDescription.Builder().addStroke(sd2).build(),null,null);


@Override
public void onCancelled(GestureDescription gestureDescription)
super.onCancelled(gestureDescription);

,null);

然后,我的疑问是:如何正确发送上述代码的鼠标坐标,可以向任何方向拖动?有什么想法吗?


编辑 3:

我找到了两个用于执行拖动的例程,但它们使用的是UiAutomation + injectInputEvent()。 AFAIK,事件注入仅适用于像 here 和 here 这样的系统应用程序,我不想要它。

这是找到的例程:

public boolean swipe(int downX, int downY, int upX, int upY, int steps, boolean drag) public boolean swipe(Point[] segments, int segmentSteps)

然后为了实现我的目标,我认为第二个例程更适合使用(遵循逻辑,不包括事件注入)与 Edit 2 上显示的代码并发送pictureBox1_MouseDownpictureBox1_MouseMove(C# Windows 窗体应用程序)分别动态填充Point[] 并在pictureBox1_MouseUp 发送cmd 执行例程并使用此数组填充。如果您对第一个例程有任何想法,请告诉我:D。

如果阅读此编辑后您有可能的解决方案,请在答案中告诉我,同时我会尝试测试这个想法。

【问题讨论】:

TeamViewer 很可能没有使用辅助功能框架。他们与设备制造商有特殊交易,这就是他们的产品并非适用于所有设备的原因。 @CommonsWare 谢谢。但我认为StrokeDescription.continueStroke() 可能是一个可能的解决方案。请参阅继续手势部分here。 关于你的第一个方法。 pictureBox1_MouseDown 不得发送坐标。它应该只存储初始坐标,然后在pictureBox1_MouseUp 上发送它们,因为这标志着鼠标移动的结束 【参考方案1】:

您是否尝试过使用AutoIt 脚本?

您可以在特定窗口/屏幕中保存坐标。 您可以在绘制图案时按住鼠标单击。

如果你想要的话,我也有一些示例代码/脚本给你!

编辑:

根据this tutorial,您可以在 C# 上使用 Auto-IT。

按照以下步骤操作:

    安装 Auto-IT 在引用管理器 (AutoItX3.dll) 中添加 Auto-IT 作为引用 然后导入您添加的库:Using AutoItX3Lib; 创建名为“auto”的新 AutoItX3 对象:AutoItX3 auto = new AutoItX3(); 您现在可以执行 Auto It 命令。

这是执行鼠标点击的完整示例:

Using AutoItX3Lib;
AutoItX3 auto = new AutoItX3();
auto.MouseClick("left", 78, 1133, 1, 35)

使用AutoIt Window Info Tool,您可以检查要使用的坐标。

请注意鼠标坐标模式之间存在差异:

例如:auto.AutoItSetOption("MouseCoordMode", 1) 将使用绝对屏幕坐标。见源here。

鼠标按住不放,可以查看MouseDown Function

【讨论】:

这没有帮助。您的建议正是我的 C# Windows 窗体应用程序已经提出的建议。【参考方案2】:

这是一个基于编辑 3 问题的解决方案示例。


C# Windows Froms 应用程序“formMain.cs”:

using System.Net.Sockets;

private List<Point> lstPoints;

private void pictureBox1_MouseDown(object sender, MouseEventArgs e) 

    if (e.Button == MouseButtons.Left)
    
        lstPoints = new List<Point>();
        lstPoints.Add(new Point(e.X, e.Y));
    


private void PictureBox1_MouseMove(object sender, MouseEventArgs e)

    if (e.Button == MouseButtons.Left)
    
        lstPoints.Add(new Point(e.X, e.Y));
    


private void PictureBox1_MouseUp(object sender, MouseEventArgs e)

    lstPoints.Add(new Point(e.X, e.Y));

    StringBuilder sb = new StringBuilder();

    foreach (Point obj in lstPoints)
    
        sb.Append(Convert.ToString(obj) + ":");
    

    serverSocket.Send("MDRAWEVENT" + sb.ToString() + Environment.NewLine);

android 服务“SocketBackground.java”:

import java.net.Socket;

String xline;

while (clientSocket.isConnected()) 

    BufferedReader xreader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8));

    if (xreader.ready()) 

        while ((xline = xreader.readLine()) != null) 
                xline = xline.trim();

            if (xline != null && !xline.trim().isEmpty()) 

                if (xline.contains("MDRAWEVENT")) 

                    String coordinates = xline.replace("MDRAWEVENT", "");
                    String[] tokens = coordinates.split(Pattern.quote(":"));
                    Point[] moviments = new Point[tokens.length];

                    for (int i = 0; i < tokens.length; i++) 
                       String[] coordinates = tokens[i].replace("", "").replace("", "").split(",");

                       int x = Integer.parseInt(coordinates[0].split("=")[1]);
                       int y = Integer.parseInt(coordinates[1].split("=")[1]);

                       moviments[i] = new Point(x, y);
                    

                    MyAccessibilityService.instance.mouseDraw(moviments, 2000);
                
            
        
    

androidAccessibilityServiceMyAccessibilityService.java”:

public void mouseDraw(Point[] segments, int time) 
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 

        Path path = new Path();
        path.moveTo(segments[0].x, segments[0].y);

        for (int i = 1; i < segments.length; i++) 

            path.lineTo(segments[i].x, segments[i].y);

            GestureDescription.StrokeDescription sd = new GestureDescription.StrokeDescription(path, 0, time);

            dispatchGesture(new GestureDescription.Builder().addStroke(sd).build(), new AccessibilityService.GestureResultCallback() 

                @Override
                public void onCompleted(GestureDescription gestureDescription) 
                    super.onCompleted(gestureDescription);
                

                @Override
                public void onCancelled(GestureDescription gestureDescription) 
                    super.onCancelled(gestureDescription);
                
            , null);
        
    

【讨论】:

【参考方案3】:

对不起,兄弟,但下面的代码和我的英语一样糟糕:

public void mouseDraw(Point[] segments, int time) 
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 

        Path path = new Path();
        path.moveTo(segments[0].x, segments[0].y);

        for (int i = 1; i < segments.length; i++) 

            path.lineTo(segments[i].x, segments[i].y);

            GestureDescription.StrokeDescription sd = new GestureDescription.StrokeDescription(path, 0, time);

            dispatchGesture(new GestureDescription.Builder().addStroke(sd).build(), new AccessibilityService.GestureResultCallback() 

                @Override
                public void onCompleted(GestureDescription gestureDescription) 
                    super.onCompleted(gestureDescription);
                

                @Override
                public void onCancelled(GestureDescription gestureDescription) 
                    super.onCancelled(gestureDescription);
                
            , null);
        
    

这里程序调用 dispatchGesture segments.length-1 次,但只有最后一个手势会完成,因为每个手势都被下一个取消。来自官方documentation:当前正在进行的任何手势,无论是来自用户、此服务还是其他服务,都将被取消。

对:

public void mouseDraw(Point[] segments, int time) 
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 
        Path path = new Path();
        path.moveTo(segments[0].x, segments[0].y);
        for (int i = 1; i < segments.length; i++) 
            path.lineTo(segments[i].x, segments[i].y);
        
        GestureDescription.StrokeDescription sd = new GestureDescription.StrokeDescription(path, 0, time);
        dispatchGesture(new GestureDescription.Builder().addStroke(sd).build(), new AccessibilityService.GestureResultCallback() 
            @Override
            public void onCompleted(GestureDescription gestureDescription) 
                super.onCompleted(gestureDescription);
            
            @Override
            public void onCancelled(GestureDescription gestureDescription) 
                super.onCancelled(gestureDescription);
            
        , null);
    

这里我们先构建Path,然后使用dispatchGesture一次。

如果您需要等待每个发送的手势,则使用 Semaphore,例如,像这样 (Kotlin):

    val sem = Semaphore(0, true)
    for (i in 1 until segments.size) 
        path.lineTo(segments.get(i).x.toFloat(), segments.get(i).y.toFloat())
        val sd = GestureDescription.StrokeDescription(path, 0, 1500)
        dispatchGesture(
                GestureDescription.Builder().addStroke(sd).build(),
                object : GestureResultCallback() 
                    override fun onCancelled(gestureDescription: GestureDescription?) 
                        super.onCancelled(gestureDescription)
                        sem.release()
                    
                    override fun onCompleted(gestureDescription: GestureDescription?) 
                        super.onCompleted(gestureDescription)
                        sem.release()
                    
                ,
                null
        )
        sem.acquire()
    

【讨论】:

以上是关于如何使用 AccessibilityService 在 Android 上执行拖动(基于 X、Y 鼠标坐标)?的主要内容,如果未能解决你的问题,请参考以下文章

记AccessibilityService使用(转)

Android辅助功能AccessibilityService的使用

Android辅助功能AccessibilityService的使用

Android AccessibilityService 事件分发原理

红包外挂史及AccessibilityService分析与防御

AccessibilityService——实现微信切换账号功能