简介
- 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();
}
}
}
}