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

44
我想知道如何在基于Android的设备上按照X、Y鼠标坐标执行拖动操作?请考虑两个简单的示例,即Team Viewer/QuickSupport在远程智能手机上绘制“密码模式”和Windows Paint的笔。{{注意:请勿写解释,保留{{和}}占位符。}}

enter image description here

enter image description here

我能做的就是模拟触摸(使用dispatchGesture()AccessibilityNodeInfo.ACTION_CLICK)。

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

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

Windows Forms应用程序:

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表单应用程序)和自定义的安卓例程进行“滑动屏幕”(参考上述链接的“滑动屏幕”代码):

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一样绘制“图案密码”)。但正如下面的评论所说,我认为使用类似的方法可以使用连续手势来实现。对于此方向上的任何建议都将受到欢迎。

enter image description here

enter image description here


编辑2:

毫无疑问,解决方案是像之前的编辑中所说的继续手势

以下是我在这里找到的假定修复代码 =>

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()。据我所知,事件注入仅在系统应用程序中工作,如herehere所述,我不想要它。

这些是找到的例程:

为了实现我的目标,我认为使用第二个例程更加适合(按照逻辑,排除事件注入),并且使用编辑2中显示的代码,将pictureBox1_MouseDownpictureBox1_MouseMove(C# Windows Forms应用程序)的所有点分别发送到动态填充的Point[]中,在pictureBox1_MouseUp上发送cmd以执行例程并使用这个填充的数组。如果您有第一个例程的想法,请告诉我:D。
如果在阅读此编辑后您有可能的解决方案,请在回答中向我展示,而我将尝试并测试这个想法。

2
TeamViewer很可能没有使用辅助功能框架。他们与设备制造商有特殊协议,这就是为什么他们的产品并非适用于所有设备的原因。 - CommonsWare
@CommonsWare 谢谢。但我认为 StrokeDescription.continueStroke() 可能是一个解决方案。请查看此处的 连续手势 部分 链接 - FLASHCODER
2
关于您的第一种方法。pictureBox1_MouseDown 不应该发送坐标。它只应该存储初始坐标,然后在 pictureBox1_MouseUp 上发送它们,因为那标志着鼠标移动的结束。 - Greggz
3个回答

2
以下是基于问题的编辑3的解决方案示例。
C# Windows Forms应用程序“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);
}

安卓服务 "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);
                }
            }
        }
    }
}

Android的 AccessibilityService "MyAccessibilityService.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);
        }
    }
}

1

抱歉,兄弟,但下面的代码就像我的英语一样糟糕:

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 次,但只有最后一个手势会被完成,因为每个手势都会被下一个手势取消。根据官方文档的描述:任何正在进行中的手势,无论是来自用户、本服务还是其他服务,都将被取消。

正确:

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()
    }

0

你尝试过使用AutoIt脚本吗?

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

如果需要,我还可以为你提供一些示例代码/脚本!


编辑:

根据这个教程,您可以在C#上使用Auto-IT。

按照以下步骤进行操作:

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

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

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


使用 AutoIt 窗口信息工具,您可以检查要使用的坐标。
请注意,鼠标坐标模式之间存在差异:
例如:auto.AutoItSetOption("MouseCoordMode", 1) 将使用绝对屏幕坐标。请参见源代码 此处


若要保持鼠标左键按下状态,您可以查看MouseDown函数


2
这并没有帮助。你的建议正是我的C# Windows表单应用程序已经实现的。 - FLASHCODER

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接