602 lines
20 KiB
Java
602 lines
20 KiB
Java
package com.itrycn.ta;
|
||
|
||
import android.accessibilityservice.AccessibilityService;
|
||
import android.accessibilityservice.GestureDescription;
|
||
import android.annotation.TargetApi;
|
||
import android.app.ActivityManager;
|
||
import android.app.DownloadManager;
|
||
import android.content.Context;
|
||
import android.content.Intent;
|
||
import android.graphics.Path;
|
||
import android.graphics.Rect;
|
||
import android.net.Uri;
|
||
import android.os.Build;
|
||
import android.os.Environment;
|
||
import android.os.Handler;
|
||
import android.provider.DocumentsContract;
|
||
import android.util.DisplayMetrics;
|
||
import android.util.Log;
|
||
import android.util.Size;
|
||
import android.view.accessibility.AccessibilityEvent;
|
||
import android.view.accessibility.AccessibilityNodeInfo;
|
||
import android.widget.Toast;
|
||
|
||
import androidx.core.content.FileProvider;
|
||
|
||
import com.itrycn.ta.script.ScriptSystemInfo;
|
||
import com.itrycn.ta.utils.AccessibilityLog;
|
||
|
||
import java.io.File;
|
||
import java.io.IOException;
|
||
import java.security.PublicKey;
|
||
import java.util.ArrayList;
|
||
import java.util.Date;
|
||
import java.util.List;
|
||
import java.util.Random;
|
||
|
||
/**
|
||
* Created by popfisher on 2017/7/11.
|
||
*/
|
||
|
||
@TargetApi(16)
|
||
public class AccessibilityOperator {
|
||
|
||
private Context mContext;
|
||
private static AccessibilityOperator mInstance = new AccessibilityOperator();
|
||
private AccessibilityEvent mAccessibilityEvent;
|
||
private AccessibilityService mAccessibilityService;
|
||
public Handler UIHandler;
|
||
public boolean IsExit=false;
|
||
private boolean result_bool=false;
|
||
public ScriptSystemInfo SystemInfo=ScriptSystemInfo.getInstance();
|
||
private AccessibilityOperator() {
|
||
}
|
||
|
||
public static AccessibilityOperator getInstance() {
|
||
return mInstance;
|
||
}
|
||
|
||
public void init(Context context) {
|
||
mContext = context;
|
||
ScriptSystemInfo.getInstance().init(context);
|
||
}
|
||
|
||
public void updateEvent(AccessibilityService service, AccessibilityEvent event) {
|
||
if (service != null && mAccessibilityService == null) {
|
||
mAccessibilityService = service;
|
||
}
|
||
if (event != null) {
|
||
mAccessibilityEvent = event;
|
||
}
|
||
}
|
||
|
||
public boolean isServiceRunning() {
|
||
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
|
||
List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(Short.MAX_VALUE);
|
||
for (ActivityManager.RunningServiceInfo info : services) {
|
||
if (info.service.getClassName().equals("com.itryn.ta.AccessibilityTaService")) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private AccessibilityNodeInfo getRootNodeInfo() {
|
||
AccessibilityEvent curEvent = mAccessibilityEvent;
|
||
AccessibilityNodeInfo nodeInfo = null;
|
||
if (Build.VERSION.SDK_INT >= 16) {
|
||
// 建议使用getRootInActiveWindow,这样不依赖当前的事件类型
|
||
if (mAccessibilityService != null) {
|
||
nodeInfo = mAccessibilityService.getRootInActiveWindow();
|
||
if(nodeInfo==null)
|
||
{
|
||
Toast("无法获取当前界面内容1");
|
||
}
|
||
//AccessibilityLog.printLog("nodeInfo: " + nodeInfo);
|
||
}
|
||
else{
|
||
Toast("辅助服务开启失败,请重启软件");
|
||
}
|
||
// 下面这个必须依赖当前的AccessibilityEvent
|
||
// nodeInfo = curEvent.getSource();
|
||
} else {
|
||
nodeInfo = curEvent.getSource();
|
||
if(nodeInfo==null)
|
||
{
|
||
Toast("无法获取当前界面内容2");
|
||
}
|
||
}
|
||
return nodeInfo;
|
||
}
|
||
|
||
/**
|
||
* 根据Text搜索所有符合条件的节点, 精确搜索方式
|
||
*/
|
||
public List<AccessibilityNodeInfo> findNodesByText(String text) {
|
||
AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
|
||
if (nodeInfo != null) {
|
||
return FindByText(nodeInfo,text);
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* 根据Text搜索所有符合条件的节点, 精确搜索方式
|
||
*/
|
||
public List<AccessibilityNodeInfo> findNodesByText(String[] text) {
|
||
AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
|
||
if (nodeInfo != null) {
|
||
return FindByText(nodeInfo,text);
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* 根据Text搜索所有符合条件的节点, 模糊搜索方式
|
||
*/
|
||
public List<AccessibilityNodeInfo> findNodesByContainsText(String text) {
|
||
AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
|
||
if (nodeInfo != null) {
|
||
return FindByContainsText(nodeInfo,text);
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* 根据Text搜索所有符合条件的节点, 模糊搜索方式
|
||
*/
|
||
public List<AccessibilityNodeInfo> findNodesByContainsText(String[] text) {
|
||
AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
|
||
if (nodeInfo != null) {
|
||
return FindByContainsText(nodeInfo,text);
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* 根据Text搜索所有符合条件的节点, 精确搜索方式
|
||
*/
|
||
public List<AccessibilityNodeInfo> FindByText(AccessibilityNodeInfo info,String text) {
|
||
List<AccessibilityNodeInfo> list=new ArrayList<AccessibilityNodeInfo>();
|
||
if (info.getChildCount() == 0 && info.getText()!=null) {
|
||
if(info.getText().toString().equals(text))
|
||
{
|
||
list.add(info);
|
||
}
|
||
} else {
|
||
for (int i = 0; i < info.getChildCount(); i++) {
|
||
if(info.getChild(i)!=null){
|
||
list.addAll(FindByText(info.getChild(i),text));
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
/**
|
||
* 根据Text搜索所有符合条件的节点, 精确搜索方式
|
||
*/
|
||
public List<AccessibilityNodeInfo> FindByText(AccessibilityNodeInfo info,String[] text) {
|
||
List<AccessibilityNodeInfo> list=new ArrayList<AccessibilityNodeInfo>();
|
||
if (info.getChildCount() == 0 && info.getText()!=null) {
|
||
for (int k=0;k<text.length;k++)
|
||
{
|
||
if(info.getText().toString().equals(text[k]))
|
||
{
|
||
list.add(info);
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
for (int i = 0; i < info.getChildCount(); i++) {
|
||
if(info.getChild(i)!=null){
|
||
list.addAll(FindByText(info.getChild(i),text));
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
/**
|
||
* 根据Text搜索所有符合条件的节点, 模糊搜索方式
|
||
*/
|
||
public List<AccessibilityNodeInfo> FindByContainsText(AccessibilityNodeInfo info,String[] text) {
|
||
List<AccessibilityNodeInfo> list=new ArrayList<AccessibilityNodeInfo>();
|
||
if (info.getChildCount() == 0 && info.getText()!=null) {
|
||
for (int k=0;k<text.length;k++)
|
||
{
|
||
if(info.getText().toString().contains(text[k]))
|
||
{
|
||
list.add(info);
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
for (int i = 0; i < info.getChildCount(); i++) {
|
||
if(info.getChild(i)!=null){
|
||
list.addAll(FindByContainsText(info.getChild(i),text));
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
/**
|
||
* 根据Text搜索所有符合条件的节点, 模糊搜索方式
|
||
*/
|
||
public List<AccessibilityNodeInfo> FindByContainsText(AccessibilityNodeInfo info,String text) {
|
||
List<AccessibilityNodeInfo> list=new ArrayList<AccessibilityNodeInfo>();
|
||
if (info.getChildCount() == 0 && info.getText()!=null) {
|
||
if(info.getText().toString().contains(text))
|
||
{
|
||
list.add(info);
|
||
}
|
||
} else {
|
||
for (int i = 0; i < info.getChildCount(); i++) {
|
||
if(info.getChild(i)!=null){
|
||
list.addAll(FindByContainsText(info.getChild(i),text));
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
/**
|
||
* 获取当前页面上的所有控件信息
|
||
*/
|
||
public List<AccessibilityNodeInfo> GetAllNodes() {
|
||
AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
|
||
if (nodeInfo != null) {
|
||
return GetAllNodes(nodeInfo);
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* 获取当前页面上的所有控件信息
|
||
*/
|
||
public List<AccessibilityNodeInfo> GetAllNodes(AccessibilityNodeInfo info) {
|
||
List<AccessibilityNodeInfo> list=new ArrayList<AccessibilityNodeInfo>();
|
||
if (info.getChildCount() == 0) {
|
||
list.add(info);
|
||
} else {
|
||
for (int i = 0; i < info.getChildCount(); i++) {
|
||
list.add(info);
|
||
if(info.getChild(i)!=null){
|
||
list.addAll(GetAllNodes(info.getChild(i)));
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
/**
|
||
* 获取当前页面上的所有没有子控件的控件信息
|
||
*/
|
||
public List<AccessibilityNodeInfo> GetAllNoChildsNodes() {
|
||
AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
|
||
if (nodeInfo != null) {
|
||
return GetAllNoChildsNodes(nodeInfo);
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* 获取当前页面上的所有没有子控件的控件信息
|
||
*/
|
||
public List<AccessibilityNodeInfo> GetAllNoChildsNodes(AccessibilityNodeInfo info) {
|
||
List<AccessibilityNodeInfo> list=new ArrayList<AccessibilityNodeInfo>();
|
||
if (info.getChildCount() == 0) {
|
||
list.add(info);
|
||
} else {
|
||
for (int i = 0; i < info.getChildCount(); i++) {
|
||
if(info.getChild(i)!=null){
|
||
list.addAll(GetAllNodes(info.getChild(i)));
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
/**
|
||
* 根据View的ID搜索符合条件的节点,精确搜索方式;
|
||
* 这个只适用于自己写的界面,因为ID可能重复
|
||
* api要求18及以上
|
||
* @param viewId
|
||
*/
|
||
public List<AccessibilityNodeInfo> findNodesById(String viewId) {
|
||
AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
|
||
if (nodeInfo != null) {
|
||
if (Build.VERSION.SDK_INT >= 18) {
|
||
return nodeInfo.findAccessibilityNodeInfosByViewId(viewId);
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
/**
|
||
* 按文本来点击
|
||
* @param text
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean clickByText(String text) {
|
||
return performClick(findNodesByText(text));
|
||
}
|
||
/**
|
||
* 按文本来点击
|
||
* @param text
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean clickByText(String[] text) {
|
||
return performClick(findNodesByText(text));
|
||
}
|
||
/**
|
||
* 按文本来点击,忽略黑名单中的控件
|
||
* @param text
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean clickByText(String[] text,Rect[] blackrect) {
|
||
runOnUiThread(new Runnable(){
|
||
public void run(){
|
||
List<AccessibilityNodeInfo> list=findNodesByText(text);
|
||
if(list==null)
|
||
{
|
||
//Toast("界面查找出错...");
|
||
result_bool=false;
|
||
}
|
||
else {
|
||
for (int i = list.size() - 1; i >= 0; i--) {
|
||
AccessibilityNodeInfo node = list.get(i);
|
||
Rect rect = new Rect();
|
||
node.getBoundsInScreen(rect);
|
||
for (int r = 0; r < blackrect.length; r++) {
|
||
if (isIntersect(rect, blackrect[r])) {
|
||
list.remove(i);
|
||
}
|
||
}
|
||
}
|
||
result_bool = performClick(list);
|
||
}
|
||
}
|
||
});
|
||
|
||
return result_bool;
|
||
}
|
||
private boolean isIntersect(Rect a, Rect b) {
|
||
return a.left <= b.right && b.left <= a.right && a.top <= b.bottom
|
||
&& b.top <= a.bottom;
|
||
}
|
||
/**
|
||
* 根据View的ID搜索符合条件的节点,精确搜索方式;
|
||
* 这个只适用于自己写的界面,因为ID可能重复
|
||
* api要求18及以上
|
||
* @param viewId
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean clickById(String viewId) {
|
||
return performClick(findNodesById(viewId));
|
||
}
|
||
|
||
public boolean performClick(List<AccessibilityNodeInfo> nodeInfos) {
|
||
if (nodeInfos != null && !nodeInfos.isEmpty()) {
|
||
AccessibilityNodeInfo node;
|
||
for (int i = 0; i < nodeInfos.size(); i++) {
|
||
node = nodeInfos.get(i);
|
||
// 获得点击View的类型
|
||
AccessibilityLog.printLog("View类型:" + node.getClassName());
|
||
// 进行模拟点击
|
||
if (node.isEnabled()) {
|
||
return ClickByNode(node);
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 从指定坐标1滑动到指定坐标2
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean Gesture(int x1,int y1,int x2,int y2,long duration){//仿滑动
|
||
if(IsExit){return false;}
|
||
if(mAccessibilityService==null){Toast("辅助服务已失效"); return false;}
|
||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||
runOnUiThread(new Runnable(){
|
||
public void run(){
|
||
Path path = new Path();
|
||
path.moveTo(x1, y1);//滑动起点
|
||
path.lineTo(x2, y2);//滑动终点
|
||
GestureDescription.Builder builder = new GestureDescription.Builder();
|
||
GestureDescription description = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, duration)).build();
|
||
//100L 第一个是开始的时间,第二个是持续时间
|
||
result_bool= mAccessibilityService.dispatchGesture(description, new MyCallBack(), null);
|
||
}
|
||
});
|
||
return result_bool;
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 从指定坐标1滑动到指定坐标2
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean Gesture(Path path,long startTime,long duration){//仿滑动
|
||
if(IsExit){return false;}
|
||
if(mAccessibilityService==null){return false;}
|
||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||
GestureDescription.Builder builder = new GestureDescription.Builder();
|
||
GestureDescription description = builder.addStroke(new GestureDescription.StrokeDescription(path, startTime, duration)).build();
|
||
//100L 第一个是开始的时间,第二个是持续时间
|
||
return mAccessibilityService.dispatchGesture(description, new MyCallBack(), null);
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 获取控件矩形参数
|
||
* @return 返回矩形
|
||
*/
|
||
public Rect GetBounds(AccessibilityNodeInfo node){
|
||
Rect rect = new Rect();
|
||
if(node!=null)
|
||
{
|
||
node.getBoundsInScreen(rect);
|
||
}
|
||
return rect;
|
||
}
|
||
/**
|
||
* 点击指定控件
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean ClickByNode(AccessibilityNodeInfo node){//点击
|
||
if(node!=null)
|
||
{
|
||
Rect rect = new Rect();
|
||
node.getBoundsInScreen(rect);
|
||
int x=(rect.left+rect.right)/2;
|
||
int y = (rect.top + rect.bottom) / 2;
|
||
//设置随机位移,防止作弊被发现
|
||
int width_d=RandomInt(0,((int)rect.width()/2)-2);
|
||
int height_d=RandomInt(0,((int)rect.height()/2)-2);
|
||
int add=RandomInt(0,1);
|
||
if(add==0)
|
||
{
|
||
x=x-width_d;
|
||
y=y-height_d;
|
||
}
|
||
else{
|
||
x=x+width_d;
|
||
y=y+height_d;
|
||
}
|
||
return Click(x,y);
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 生成随机数
|
||
* @return
|
||
*/
|
||
public int RandomInt(int min,int max)
|
||
{
|
||
return (new Random()).nextInt((max - min) + 1) + min;
|
||
}
|
||
/**
|
||
* 生成随机数
|
||
* @return
|
||
*/
|
||
public long RandomLong(long min,long max)
|
||
{
|
||
Random random = new Random();
|
||
return ( long )random.nextDouble()*(max-min) + min;
|
||
}
|
||
/**
|
||
* 点击指定坐标
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean Click(int x,int y){//点击
|
||
return Press(x,y,150);
|
||
}
|
||
|
||
/**
|
||
* 长点击指定坐标
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean LongClick(int x,int y){//点击
|
||
return Press(x,y,700);
|
||
}
|
||
/**
|
||
* 点击指定坐标
|
||
* @return 是否点击成功
|
||
*/
|
||
public boolean Press(int x,int y,long delay){//点击
|
||
return Gesture(x,y,x,y,delay);
|
||
}
|
||
/**
|
||
* 运行app
|
||
* @return 是否成功
|
||
*/
|
||
public boolean LaunchApp(String packageName)
|
||
{
|
||
if(IsExit){return false;}
|
||
Intent intent =mContext.getPackageManager().getLaunchIntentForPackage(packageName);
|
||
if (intent != null) {
|
||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||
mContext.startActivity(intent);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 安装应用
|
||
* @param Path
|
||
*/
|
||
private void _InstallApk(String Path) {
|
||
|
||
Uri uri;
|
||
File apkFile;
|
||
try {
|
||
apkFile = new File(Path);
|
||
if(!apkFile.exists()){return;}
|
||
Intent intentInstall = new Intent(Intent.ACTION_VIEW);
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
|
||
|
||
Uri contentUri = FileProvider.getUriForFile(mContext, "com.rfid.application.MyApplication.provider", apkFile);
|
||
intentInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||
intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||
intentInstall.setDataAndType(contentUri, "application/vnd.android.package-archive");
|
||
|
||
}else{
|
||
intentInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||
intentInstall.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
|
||
|
||
}
|
||
mContext.startActivity(intentInstall);
|
||
}
|
||
catch (Exception e) {
|
||
// TODO Auto-generated catch block
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
/**
|
||
* 休眠多少毫秒
|
||
*/
|
||
public void Sleep(long millis) {
|
||
Date date=new Date();
|
||
long addtime= date.getTime()+millis;
|
||
while(addtime>=(new Date()).getTime() && !IsExit) {
|
||
try {
|
||
Thread.sleep(10);
|
||
}
|
||
catch (Exception e){e.printStackTrace();}
|
||
}
|
||
}
|
||
/**
|
||
* 休眠多少毫秒
|
||
*/
|
||
public void Sleep(long millis_min,long millis_max) {
|
||
long R_millis=RandomLong(millis_min,millis_max);
|
||
Sleep(R_millis);
|
||
}
|
||
/**
|
||
*Toast提示
|
||
*/
|
||
public void Toast(String text)
|
||
{
|
||
runOnUiThread(new Runnable(){
|
||
public void run(){
|
||
Toast.makeText(mContext.getApplicationContext(), text, Toast.LENGTH_SHORT).show();
|
||
}
|
||
});
|
||
}
|
||
public final void runOnUiThread(Runnable action) {
|
||
if(UIHandler==null) {
|
||
action.run();
|
||
}else
|
||
{
|
||
UIHandler.post(action);
|
||
}
|
||
}
|
||
/**
|
||
*点击返回键
|
||
*/
|
||
public boolean clickBackKey() {
|
||
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
|
||
}
|
||
|
||
private boolean performGlobalAction(int action) {
|
||
if(IsExit){return false;}
|
||
if(mAccessibilityService==null){return false;}
|
||
runOnUiThread(new Runnable(){
|
||
public void run(){
|
||
result_bool= mAccessibilityService.performGlobalAction(action);
|
||
}
|
||
});
|
||
return result_bool;
|
||
}
|
||
}
|