简介

  • AccessibilityService 是谷歌开放的一个帮助残障人士操作手机的一个API。这个API就像是电脑版的案件精灵,借助这个API可以实现很多自动化的功能 或 对手机的监控,信息采集。

创建自己的AccessibilityService

  • AndroidManifest.xml
    ···xml

<?xml version="1.0" encoding="utf-8"?>

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_account_set_active"
    android:label="@string/app_name"
    android:networkSecurityConfig="@xml/network_security_config"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.WechatTools">

    <meta-data
        android:name="com.google.android.actions"
        android:resource="@xml/accessible_service_config_ali" />

    <activity
        android:name="com.wt.wechatTools.MainActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <action android:name="com.example.wechatredpacket.MainActivity" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity> <!-- BIND_ACCESSIBILITY_SERVICE确保只有系统可以绑定该服务 -->
    <service
        android:name="com.wt.wechatTools.service.RedPacketService"
        android:enabled="true"
        android:exported="true"
        android:label="WECHAT_TS"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService" />
        </intent-filter>

        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/accessible_service_config_ali" />
    </service>
</application>

···

accessible_service_config_ali

<?xml version ="1.0" encoding ="utf-8"?><!--  Learn More about how to use App Actions: https://developer.android.com/guide/actions/index.html -->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagReportViewIds"
    android:canRetrieveWindowContent="true"
    android:notificationTimeout="400"
    android:canPerformGestures="true"
    android:packageNames="com.tencent.mm" />

<!--    android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"-->
<!--参考 https://www.jianshu.com/p/7b91e3702328-->
<!--    canRetrieveWindowContent 允许服务检索窗口内容 android:canRetrieveWindowContent="true"-->
<!--    canPerformGestures	是否可以执行手势(api 24新增)-->
<!--    notificationTimeout	两个相同类型的可访问性事件之间的最短间隔时间(以毫秒为单位)-->
<!--    accessibilityFeedbackType	指定反馈方式-->

<!--    accessibilityEventTypes	指定要监听的事件类型-->
<!--    typeAllMask:接收所有事件。-->
<!--    -------窗口事件相关(常用)----------->
<!--    typeWindowStateChanged:监听窗口状态变化,比如打开一个popupWindow,dialog,Activity切换等等。-->
<!--    typeWindowContentChanged:监听窗口内容改变,比如根布局子view的变化。-->
<!--    typeWindowsChanged:监听屏幕上显示的系统窗口中的事件更改。 此事件类型只应由系统分派。-->
<!--    typeNotificationStateChanged:监听通知变化,比如notifacation和toast。-->
<!--    -----------View事件相关---------------->
<!--    typeViewClicked:监听view点击事件。-->
<!--    typeViewLongClicked:监听view长按事件。-->
<!--    typeViewFocused:监听view焦点事件。-->
<!--    typeViewSelected:监听AdapterView中的上下文选择事件。-->
<!--    typeViewTextChanged:监听EditText的文本改变事件。-->
<!--    typeViewHoverEnter、typeViewHoverExit:监听view的视图悬停进入和退出事件。-->
<!--    typeViewScrolled:监听view滚动,此类事件通常不直接发送。-->
<!--    typeViewTextSelectionChanged:监听EditText选择改变事件。-->
<!--    typeViewAccessibilityFocused:监听view获得可访问性焦点事件。-->
<!--    typeViewAccessibilityFocusCleared:监听view清除可访问性焦点事件。-->
<!--    ------------手势事件相关----------------->
<!--    typeGestureDetectionStart、typeGestureDetectionEnd:监听手势开始和结束事件。-->
<!--    typeTouchInteractionStart、typeTouchInteractionEnd:监听用户触摸屏幕事件的开始和结束。-->
<!--    typeTouchExplorationGestureStart、typeTouchExplorationGestureEnd:监听触摸探索手势的开始和结束。-->

java 启动服务

Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
                startActivity(intent);

自定义的RedPacketService

package com.wt.wechatTools.service;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.Builder;
import android.app.Application;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import com.wt.wechatTools.room.WorkRepository;
import com.wt.wechatTools.room.entity.ContactsEntity;
import com.wt.wechatTools.room.entity.UserEntity;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

//https://www.jianshu.com/p/4cd8c109cdfb
//https://www.jianshu.com/p/7b91e3702328
//

public class RedPacketService extends AccessibilityService {
    private static final String TAG = "RedPacketService";
    private WorkRepository workRepository;
    private List<ContactsEntity> contactsEntityList; //任务目录
    private ContactsEntity contactsEntity;
    private List<ContactsEntity> cnUpdateList = new ArrayList<>(); //在任务结束时需要更新的目录
    private Integer delay;
    private Integer tastBatch;
    private UserEntity userEntity;

    //悬浮框
    public static boolean isStarted = false;
    private WindowManager windowManager;
    private WindowManager.LayoutParams layoutParams;
    private Button btnStart;

    @Override
    public void onCreate() {
        super.onCreate();
        isStarted = true;
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        layoutParams = new WindowManager.LayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        layoutParams.format = PixelFormat.RGBA_8888;
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        layoutParams.width = 300;
        layoutParams.height = 166;

        layoutParams.x = 300;
        layoutParams.y = 800;

        //x = 1440,y = 2960
    }

    //        <Button
//    android:id="@+id/btnTaskList"
//    android:layout_width="wrap_content"
//    android:layout_height="wrap_content"
//    android:text="设置任务清单"
//    android:textSize="18sp"
//    app:layout_constraintBottom_toBottomOf="@+id/textView8"
//    app:layout_constraintEnd_toEndOf="@+id/btnXGFW"
//    app:layout_constraintTop_toTopOf="@+id/textView8" />
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void showFloatingWindow() {
        if (Settings.canDrawOverlays(this)) {
            btnStart = new Button(getApplicationContext());
            btnStart.setText("启动服务");
            btnStart.setBackgroundColor(Color.RED);
            windowManager.addView(btnStart, layoutParams);

            /**
             * 启动服务
             */
            btnStart.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Log.d(TAG, "showFloatingWindow: 任务开始了!");
                    //获取任务数据
                    getData();
                }
            });

            /**
             * 移动位置
             */
            btnStart.setOnTouchListener(new FloatingOnTouchListener());
        }
    }

    /**
     * 拖动功能
     */
    private class FloatingOnTouchListener implements View.OnTouchListener {
        private int x;
        private int y;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int nowX = (int) event.getRawX();
                    int nowY = (int) event.getRawY();
                    int movedX = nowX - x;
                    int movedY = nowY - y;
                    x = nowX;
                    y = nowY;
                    layoutParams.x = layoutParams.x + movedX;
                    layoutParams.y = layoutParams.y + movedY;
                    windowManager.updateViewLayout(view, layoutParams);
                    break;
                default:
                    break;
            }
            return false;
        }
    }

//    @RequiresApi(api = Build.VERSION_CODES.M)
//    @Override
//    public int onStartCommand(Intent intent, int flags, int startId) {
//        Log.d(TAG, "onStartCommand: 1111111111111111111111111");
//        showFloatingWindow();
//        return super.onStartCommand(intent, flags, startId);
//    }

    /**
     * 获取任务数据
     */
    private void getData() {
        MyHandler myHandler = new MyHandler(getApplication(), this);
        //获取数据
        new Thread() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            public void run() {
                Message message = new Message();
                //初始化WorkRepository 数据仓库方法
                workRepository = new WorkRepository(getApplicationContext());
                //获取任务参数
                userEntity = workRepository.getUser();
                Integer cs = userEntity.getCs();
                Integer count = userEntity.getCount();

                String endTimeString = userEntity.getEndTime();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                Date nowDate = new Date();

                try {
                    Date endTime = sdf.parse(endTimeString);
                    if (endTime.getTime() > nowDate.getTime() || cs > count) {
                        //任务批次
                        tastBatch = userEntity.getTaskBatch();
                        tastBatch++;
                        //任务间隔
                        delay = userEntity.getSpeed();
                        //构造需要更新的useEntity数据
                        userEntity.setTaskBatch(tastBatch);
                        if (cs > count) {
                            userEntity.setCs(cs - count);
                        }
                        //更新数据
                        workRepository.updateUser(userEntity);
                        //获取任务清单
                        contactsEntityList = workRepository.getTaskList(count);
                        if (contactsEntityList.size() == 0) {
                            //向主进程推送消息:未获取到任务清单
                            message.what = 1;
                            message.obj = "未获取到任务清单!";
                            myHandler.sendMessage(message);
                            return;
                        }
                        //开始任务
                        clickContact();
                    } else {
                        message.what = 1;
                        message.obj = "请充值!";
                        myHandler.sendMessage(message);
                        return;
                    }
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
        }.start();
        btnStart.setText("任务已开始,请勿触碰!");
    }

    //使用服务的过程中如果出现异常,服务会自动停止。
    //异步的方法
    //https://www.runoob.com/w3cnote/android-tutorial-handler-message.html
    //
    private static class MyHandler extends Handler {
        //初始化这个类
        private Application application;
        private RedPacketService redPacketService;

        public MyHandler(Application application, RedPacketService redPacketService) {
            this.application = application;
            this.redPacketService = redPacketService;
        }

        @RequiresApi(api = Build.VERSION_CODES.N)
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1: //未获取到任务清单、请充值
                    Toast.makeText(application.getBaseContext(), msg.obj.toString(), Toast.LENGTH_LONG).show();
                    //关闭服务
                    redPacketService.disableSelf();
                    break;
            }
        }
    }

    /**
     * 当无障碍服务关闭时会调用此方法。
     */
    @Override
    public boolean onUnbind(Intent intent) {
        //销毁悬浮窗
        if (windowManager != null && btnStart != null) {
            windowManager.removeView(btnStart);
            btnStart = null;
        }
        Toast.makeText(this, "任务执行结束!WECHAT_TS已停止!", Toast.LENGTH_SHORT).show();
        if (cnUpdateList.size() == 0) {
            Log.d(TAG, "onUnbind: 服务执行失败了!");
        } else {
            Log.d(TAG, "onUnbind: 开始执行本地数据更新的任务!" + cnUpdateList.size());
            new Thread() {
                @Override
                public void run() {
                    //数据库操作
                    workRepository.updateContactList(cnUpdateList);
                    workRepository.updateUser(userEntity);
                }
            }.start();
        }
        return super.onUnbind(intent);
    }

    /**
     * 打开无障碍服务时调用此方法
     * 获取基础数据
     * 系统成功绑定该服务时被触发,也就是当你在设置中开启相应的服务,系统成功的绑定了该服务时会触发,通常我们可以在这里做一些初始化操作
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        //销毁悬浮窗
        if (windowManager != null && btnStart != null) {
            windowManager.removeView(btnStart);
            btnStart = null;
        }

        showFloatingWindow();

        //打开微信
//        Intent lan = getPackageManager().getLaunchIntentForPackage("com.tencent.mm");
//        Intent intent = new Intent(Intent.ACTION_MAIN);
//        intent.addCategory(Intent.CATEGORY_LAUNCHER);
//        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//        intent.setComponent(lan.getComponent());
//        startActivity(intent);
//        https://www.jianshu.com/p/1adb4a6b8618

        //返回桌面
//        performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);

//        new Handler().postDelayed(new Runnable() {
//            @Override
//            public void run() {
//        //切换至微信首页
//        try {
//            Log.d(TAG, "onServiceConnected: 启动微信 开始");
//            Intent intent = new Intent();
//            //查看当前活动的窗口的包名 和 acitive  adb shell dumpsys window | findstr mCurrentFocus
//            //https://www.jianshu.com/p/42ae7066f8f3
//            //1、需要将目标Activity的android:exported="true"属性在所属应用AndroidMainfest里设置为true,意思是当前Activity可以被外部应用访问,否则会报下面的错误
//            //ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI");
//            ComponentName cmp = new ComponentName("com.example.wechatredpacket", "com.example.wechatredpacket.MainActivity");
//            intent.setAction(Intent.ACTION_MAIN);
//            intent.addCategory(Intent.CATEGORY_LAUNCHER);
//            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//            intent.setComponent(cmp);
//            startActivity(intent);
//        } catch (Exception e) {
//            Log.d(TAG, "onServiceConnected: 启动微信 出错了!");
//        }
//            }
//        },1000);
    }

    /**
     * 目前仅监听了typeWindowStateChanged事件
     * 当系统检测到与无障碍服务指定的事件过滤参数匹配的 AccessibilityEvent
     * 时,就会回调此方法。例如,当用户点击按钮,或者聚焦于某个应用(无障碍
     * 服务正在为该应用提供反馈)中的界面控件时。出现这种情况时,系统会调用
     * 此方法,并传递关联的 AccessibilityEvent,然后服务会对该类进行解释并
     * 使用它来向用户提供反馈。此方法可能会在您的服务的整个生命周期内被调用多次。
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
//        rootNodeInfo = event.getSource();
//        if (rootNodeInfo == null) {
//            return;
//        }
//        Log.d(TAG, "onAccessibilityEvent: "+rootNodeInfo);
//        switch (step) {
//            case "begin":
//                clickContact(rootNodeInfo);
//                break;
//            case "other":
//                Log.d(TAG, "onAccessibilityEvent: typeWindowStateChanged----------other");
//                break;
//
//        }
    }

    /**
     * 当系统想要中断您的服务正在提供的反馈(通常是为了响应将焦点移到其他
     * 控件等用户操作)时,就会调用此方法。此方法可能会在您的服务的整个生命
     * 周期内被调用多次。
     */
    @Override
    public void onInterrupt() {

    }


    /**
     * 未使用
     * 检查包是否存在
     *
     * @param packname
     * @return
     */
    private boolean checkPackInfo(String packname) {
        PackageInfo packageInfo = null;
        try {
            packageInfo = getPackageManager().getPackageInfo(packname, 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return packageInfo != null;
    }

    /**
     * 第一步 :获取 通讯录 并点击
     *
     * @return 是否操作成
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void clickContact() {
        try {
            Thread.sleep(500);
            Log.d(TAG, "clickContact: bengin");
            AccessibilityNodeInfo nodeContact = getNodeBase("通讯录");
            AccessibilityNodeInfo nodeInfoParent = nodeContact.getParent();
            // 点击
            boolean isClick = nodeInfoParent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            if (isClick) {
                Log.d(TAG, "clickContact: 点击 通讯录 成功!");
                //新的朋友
                clickNewFriend();
                return;
            } else {
                Log.d(TAG, "clickContact: 点击 通讯录 失败!");
                return;
            }
        } catch (Exception e) {
            Log.e(TAG, "clickContact: 错误", e);
            return;
        }
    }

    /**
     * 第二步 :获取 新的朋友 并点击
     *
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void clickNewFriend() {
        try {
            Thread.sleep(delay);
            AccessibilityNodeInfo nodeInfo = getNodeBase("新的朋友");
            AccessibilityNodeInfo nodeEnd = nodeInfo.getParent().getParent();
            //点击
            boolean isClick = nodeEnd.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            if (isClick) {
                Log.d(TAG, "clickNewFriend: 点击 新的朋友 成功! ");
                clickAddFriend();
            } else {
                Log.d(TAG, "clickNewFriend: 点击 新的朋友 失败! ");
            }
        } catch (Exception e) {
            Log.e(TAG, "clickNewFriend: 错误", e);
        }
    }

    /**
     * 未使用
     * 第二步 :点击 添加朋友
     *
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void clickAddFriend() {
        try {
            AccessibilityNodeInfo nodeEnd = getNodeBase("添加朋友");

            //点击
            boolean isClick = nodeEnd.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            if (isClick) {
                Log.d(TAG, "clickAddFriend: 点击 添加朋友 成功! ");
                clickText();
            } else {
                Log.d(TAG, "clickAddFriend: 点击 添加朋友 失败! ");
            }
        } catch (Exception e) {
            Log.e(TAG, "clickAddFriend: 错误", e);
        }
    }

    /**
     * 获取节点  尝试获取10次节点,如果超过10次没有获取到节点那么就 关闭服务。
     *
     * @param nodeText
     * @return
     */
    private AccessibilityNodeInfo getNodeBase(String nodeText) {
        AccessibilityNodeInfo nodeBase = null;
        try {
            Integer count = 0;
            while (count < 10) {
                nodeBase = getRootInActiveWindow();
                if (nodeBase == null) {
                    Log.d(TAG, "clickText: 未获取到根节点");
                    continue;
                } else {
                    List<AccessibilityNodeInfo> list = nodeBase.findAccessibilityNodeInfosByText(nodeText);
                    if (list.size() == 1) {
                        Log.d(TAG, "clickText: 找到节点");
                        nodeBase = list.get(0);
                        break;
                    }
                }
                Thread.sleep(delay);
                count++;
            }
            //如果到第10次都没有获取到节点,那么结束服务
            if (nodeBase == null) {
                //结束任务
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    this.disableSelf();
                }
            }
        } catch (Exception e) {
            Log.d(TAG, "getNodeBase: " + e);
        }
        return nodeBase;
    }

    /**
     * 第四步 :点击 微信号/手机号
     *
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void clickText() {
        try {
            Thread.sleep(delay);
            AccessibilityNodeInfo nodeInfo = getNodeBase("微信号/手机号");
//            AccessibilityNodeInfo nodeEnd = nodeInfo.getParent();
//            boolean isClick = nodeEnd.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//            if (isClick) {
//                Log.d(TAG, "clickText: 点击 微信号/手机号 成功!");
//                printPhone();
//            } else {
//                Log.d(TAG, "clickText: 点击 微信号/手机号 失败!");
//            }

//            模拟点击
            Rect rect = new Rect();
            nodeInfo.getBoundsInScreen(rect); //获取控件位置
            Point position = new Point(rect.left, rect.top);

            Builder builder = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                builder = new Builder();
            }
            Path p = new Path();
            p.moveTo(position.x, position.y);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                builder.addStroke(new GestureDescription.StrokeDescription(p, 100, 50));
            }
            GestureDescription gesture = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                gesture = builder.build();
            }
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                dispatchGesture(gesture, new GestureResultCallback() {
                    @Override
                    public void onCompleted(GestureDescription gestureDescription) {
                        super.onCompleted(gestureDescription);
                        Log.d(TAG, "touchText: 模拟点击成功");
                        printPhone();
                    }

                    //取消手势后调用,暂时用不到
                    @Override
                    public void onCancelled(GestureDescription gestureDescription) {
                        super.onCancelled(gestureDescription);
                        Log.d(TAG, "touchText: 取消手势..........");
                    }
                }, null);
            }
        } catch (Exception e) {
            Log.e(TAG, "clickText: 错误", e);
        }
    }

    /**
     * 输入操作
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void printPhone() {
        try {
            Log.d(TAG, "printPhone: printPhone");
            Thread.sleep(delay);
//           参考 https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityService?hl=en#findFocus(int)
            //查找当前具有焦点的输入框
            AccessibilityNodeInfo nodeEditText = null;
            Integer cc = 0;
            while (cc < 10) {
                nodeEditText = findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
                if (nodeEditText != null) {
                    break;
                }
                cc++;
                Thread.sleep(800);
            }
            if (nodeEditText == null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    this.disableSelf();
                }
            }

            //获取当前页面的根节点
            //https://developer.android.google.cn/reference/kotlin/android/accessibilityservice/AccessibilityService?hl=en#getrootinactivewindow
//            AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
//            if (nodeInfo == null) {
//                Log.d(TAG, "printPhone: 未找到根节点");
//                return;
//            }
//            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("微信号/手机号");
//            if (list.size() == 0) {
//                Log.d(TAG, "printPhone: 未找到节点");
//                return;
//            }
//            AccessibilityNodeInfo nodeEditText = nodeInfo.getChild(1);

            //将要粘贴的手机号码,加入系统剪切板。
            //ClipboardManager中文意思就是剪切板的管理者,如果要进行复制剪切操作就要用到它
            //系统剪切板的调用服务
            //创建剪切板管理对象
//            ClipboardManager cm = (ClipboardManager) getApplication().getSystemService(Context.CLIPBOARD_SERVICE);
//            //设置剪切板内容
//            ClipData clip = ClipData.newPlainText(userNumber, userNumber);
//            cm.setPrimaryClip(clip);
//            if (cm.getPrimaryClip() == null) {
//                Log.d(TAG, "copyAndPaste: 设置剪切板失败!");
//                return;
//            }

            //获取 微信号/手机号
//            List<AccessibilityNodeInfo> list = rootNodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/bxz");
//            if(list.size() == 0)
//            {
//                Log.d(TAG, "copyAndPaste: 未找到--微信号/手机号--控件");
//                return;
//            }
//            AccessibilityNodeInfo nodeEditText = list.get(0);

//            获取这次任务需要的电话
            if (contactsEntityList.size() == 0) {
                //关闭服务
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    //结束任务
                    this.disableSelf();
                    return;
                }
            }

            contactsEntity = contactsEntityList.get(0);
            //准备数据
            ContactsEntity cn = new ContactsEntity(contactsEntity.getId(), contactsEntity.getName(), contactsEntity.getCellPhone(), 2, tastBatch);
            //加入集合用于在任务结束时,更新数据。
            cnUpdateList.add(cn);
            //删除元素
            contactsEntityList.remove(0);

            //andorid 10以后的版本在后台执行的程序,无法读取剪切板。同时目前遇到一个无法设置剪切板的bug,只有第一次能设置成功。
            boolean isOK = false;
            Log.d(TAG, "printPhone: " + contactsEntity.getCellPhone());
//                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//                        Bundle arguments = new Bundle();
//                        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
//                        arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, userNumber.length());
//                        nodeEditText.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
//                        nodeEditText.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
//                        isOK = nodeEditText.performAction(AccessibilityNodeInfo.ACTION_PASTE);
//                    } else
            //输入内容
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Bundle arguments = new Bundle();
                arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, contactsEntity.getCellPhone());
                nodeEditText.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
                isOK = nodeEditText.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
            } else {
                //
                Log.d(TAG, "run: 您的系统版本不支持输入。需要android 5.0以上的系统");
            }
            if (isOK) {
                Log.d(TAG, "printPhone: 输入成功!" + contactsEntity.getCellPhone());
                search();
            } else {
                Log.d(TAG, "printPhone: 输入失败!" + contactsEntity.getCellPhone());
            }
//                    // 清空剪切板
//                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//                        cm.clearPrimaryClip();
//                        Log.d(TAG, "copyAndPaste: 使用clearPrimaryClip() 清空剪切板成功!");
//                    } else {
//                        if (cm != null) {
//                            try {
//                                cm.setPrimaryClip(cm.getPrimaryClip());
//                                cm.setText(null);
//                                Log.d(TAG, "copyAndPaste: 使用setText(null) 清空剪切板成功!");
//                            } catch (Exception e) {
//                                Log.d(TAG, "copyAndPaste: " + e);
//                            }
//                        }
//                    }
        } catch (Exception e) {
            Log.e(TAG, "printPhone: 错误", e);
        }
    }

    /**
     * 点击 搜索
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void search() {
        try {
            Thread.sleep(delay);
            Log.d(TAG, "search: ");
            //获取当前页面的根节点
            //https://developer.android.google.cn/reference/kotlin/android/accessibilityservice/AccessibilityService?hl=en#getrootinactivewindow
            AccessibilityNodeInfo nodeInfo = getNodeBase("搜索:" + contactsEntity.getCellPhone());
            AccessibilityNodeInfo nodeEnd = nodeInfo.getParent();
            boolean isClick = nodeEnd.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            if (isClick) {
                Log.d(TAG, "search: 点击 搜索:" + contactsEntity.getCellPhone() + "成功!");
                //添加到通讯录
                addContacts();
                return;
            } else {
                Log.d(TAG, "search: 点击 搜索:" + contactsEntity.getCellPhone() + "失败!");
                return;
            }
        } catch (Exception e) {
            Log.e(TAG, "search: 错误", e);
        }
    }

    /**
     * 添加到通讯录
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void addContacts() {
        try {
            Log.d(TAG, "search2: ");
            Thread.sleep(delay);
            //获取根节点
            AccessibilityNodeInfo nodeBase = null;
            Integer addCount = 0;
            while (addCount < 10) {
                nodeBase = getRootInActiveWindow();
                if (nodeBase != null) {
                    break;
                }
                addCount++;
                if (addCount == 10) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        this.disableSelf();
                    }
                }
                Thread.sleep(800);
            }

            //关闭服务
            if (nodeBase == null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    this.disableSelf();
                }
                return;
            }

            //判断是否能够搜索到此用户
            List<AccessibilityNodeInfo> isCheck = nodeBase.findAccessibilityNodeInfosByText("该用户不存在");
            if (isCheck.size() == 1) {
                Log.d(TAG, "search2: 该用户不存在");
                // 已经是好友了,该用户不存在,也会导致无法找到关键字  添加到通讯录。
                //那么,就将这一列,先放到临时的集合里。在任务结束时,更新数据。就更新此好友的状态为3(无法查找)
                ContactsEntity cn = new ContactsEntity(contactsEntity.getId(), contactsEntity.getName(), contactsEntity.getCellPhone(), 3, tastBatch);
                //加入集合用于在任务结束时,更新数据。
                cnUpdateList.add(cn);
                //进入输入流程
                backInsert1();
                return;
            }

            //判断此用户是否已添加
            List<AccessibilityNodeInfo> isAdd = nodeBase.findAccessibilityNodeInfosByText("发消息");
            if (isAdd.size() == 1) {
                Log.d(TAG, "search2: 该用户已添加");
                // 已经是好友了,该用户不存在,也会导致无法找到关键字  添加到通讯录。
                //那么,就将这一列,先放到临时的集合里。在任务结束时,更新数据。就更新此好友的状态为3(无法查找)
                ContactsEntity cn = new ContactsEntity(contactsEntity.getId(), contactsEntity.getName(), contactsEntity.getCellPhone(), 4, tastBatch);
                //加入集合用于在任务结束时,更新数据。
                cnUpdateList.add(cn);
                backInsert2();
                return;
            }

            //判断被搜帐号状态异常,无法显示
            List<AccessibilityNodeInfo> isError = nodeBase.findAccessibilityNodeInfosByText("被搜帐号状态异常,无法显示");
            if (isError.size() == 1) {
                Log.d(TAG, "search2: 被搜帐号状态异常,无法显示");
                // 已经是好友了,该用户不存在,也会导致无法找到关键字  添加到通讯录。
                //那么,就将这一列,先放到临时的集合里。在任务结束时,更新数据。就更新此好友的状态为3(无法查找)
                ContactsEntity cn = new ContactsEntity(contactsEntity.getId(), contactsEntity.getName(), contactsEntity.getCellPhone(), 3, tastBatch);
                //加入集合用于在任务结束时,更新数据。
                cnUpdateList.add(cn);
                backInsert1();
                return;
            }

            //执行添加好友流程
            List<AccessibilityNodeInfo> list = nodeBase.findAccessibilityNodeInfosByText("添加到通讯录");
            if (list.size() == 0) {
                Log.d(TAG, "search2: 未找到  添加到通讯录");
            }else {
                AccessibilityNodeInfo nodeInfo = list.get(0);
                AccessibilityNodeInfo nodeEnd = nodeInfo.getParent();
                boolean isClick = nodeEnd.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                if (isClick) {
                    Log.d(TAG, "search2: 点击 添加到通讯录 成功!");
                    //这里会有2中情况,一种是直接就通过了好友请求。
                    //第二种是:申请添加朋友 页面  发起好友验证请求
                    //如果,你被添加成了黑名单,点击 添加到通讯录后,将无法跳转。
                    sendNew();
                    return;
                } else {
                    Log.d(TAG, "search2: 点击 添加到通讯录 失败!");
                }
            }

            //程序卡了   关闭服务
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                //结束任务
                this.disableSelf();
            }
        } catch (Exception e) {
            Log.e(TAG, "search2: 错误", e);
        }
    }

    /**
     * 发送好友请求  申请添加朋友
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void sendNew() {
        try {
            //获取根节点
            AccessibilityNodeInfo nodeBase = null;
            List<AccessibilityNodeInfo> list = new ArrayList<>();
            Integer number = 0;
            while (number < 10) {
                Thread.sleep(1000);
                nodeBase = getRootInActiveWindow();
                if (nodeBase != null) {
                    list = nodeBase.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/d6");
                    if (list.size() == 1) {
                        break;
                    }
                }
                number++;
                Thread.sleep(800);
            }

            //如果,没有找到标签。重新开始的流程。
            if (list.size() == 0) {
                //保存数据
                ContactsEntity cn = new ContactsEntity(contactsEntity.getId(), contactsEntity.getName(), contactsEntity.getCellPhone(), 3, tastBatch);
                //加入集合用于在任务结束时,更新数据。
                cnUpdateList.add(cn);
                backInsert2();
                return;
            }

            Thread.sleep(1000);
            AccessibilityNodeInfo nodeInfo = list.get(0);
            boolean isClick = nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            if (isClick) {
                //有时候,点击了发送按钮后,会没有反应
                Log.d(TAG, "send: 点击 发送 成功!");
                backInsert2();
                return;
            } else {
                Log.d(TAG, "send: 点击 发送 失败!");
                return;
            }
        } catch (Exception e) {
            Log.d(TAG, "send: " + e);
        }
    }

    /**
     * 返回到输入界面
     */
    private void backInsert1() {
        //发送成功后,点击返回按钮, 回到上一级页面,继续输入新的用户
        try {
            Thread.sleep(delay);
            performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
            Thread.sleep(delay);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                clickText();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回到输入界面
     */
    private void backInsert2() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            try {
                Thread.sleep(delay);
                performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
                Thread.sleep(delay);
                performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
                clickText();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}