辅助服务实现自动化 记录

Accessibility的使用

配置

  • 创建一个继承AccessibilityService的服务,实现其方法
1
2
3
4
5
6
7
8
9
10
11
public class MonitorService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
}

onAccessibilityEvent(AccessibilityEvent event):响应AccessibilityEvent的事件,在用户操作的过程中,系统不断的发送sendAccessibiltyEvent(AccessibilityEvent event);然后通过onAccessibilityEvent()可以捕捉到该事件,然后分析。

onInterrupt():打断获取事件时调用

  • 在在AndroidMainifest对其进行配置
1
2
3
4
5
6
7
8
9
10
11
<service
android:name=".service.MonitorService"
android:label="@string/app_name"
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/monitor_service_config" />
</service>

其中 monitor_service_config 是res目录下xml文件夹中的配置文件

1
2
3
4
5
6
7
8
9
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_description"
android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"
android:packageNames="jp.co.benesse.android.tamahiyo.pregnantcounter"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:accessibilityFlags=""
android:canRetrieveWindowContent="true"/>

description 是辅助服务的描述,在手机设置 辅助功能 开启时会进行显示
accessibilityEventTypes 是响应事件的类型
packageNames 是要辅助的应用包名,可以设置多个包名
accessibilityFeedbackType 反馈响应的类型

实现功能

注意事项 (atom预览Shift + Ctrl + M)

时刻注意Activity的变化,

模拟输入

ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(label, text);
clipboard.setPrimaryClip(clip);
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);

ClipData

newPlainText(label, text)返回ClipData对象,数据是文字text,描述是label,MIME类型是MIMETYPE_TEXT_PLAIN。

避免EditText自动获取焦点

在其父控件设置

1
2
android:focusable="true"
android:focusableInTouchMode="true"

获取桌面的包名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 获取运行的桌面包名
* @param context 上下文
* @return 有则返回包名,否则返回null
*/
public static String getLauncherPackageName(Context context) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
final ResolveInfo res = context.getPackageManager().resolveActivity(intent, 0);
if (res.activityInfo == null) {
// should not happen. A home is always installed, isn't it?
return null;
}
if (res.activityInfo.packageName.equals("android")) {
// 有多个桌面程序存在,且未指定默认项时;
return null;
} else {
return res.activityInfo.packageName;
}
}

模拟滑动

http://blog.csdn.net/ouyang_peng/article/details/48463059
http://blog.csdn.net/mad1989/article/details/38109689/
http://blog.csdn.net/huiguixian/article/details/11925389
以上是一些模拟事件的文章,其中

input swipe 250 250 300 300

虽然有了滑动的动作能够看到轨迹,但是模拟器上,界面并没有产生滑动的翻页或者上下滑动页面的效果。

通过查看input的帮助,发现了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Usage: input [<source>] <command> [<arg>...]
The sources are:
trackball
joystick
touchnavigation
mouse
keyboard
gamepad
touchpad
dpad
stylus
touchscreen
The commands and default sources are:
text <string> (Default: touchscreen)
keyevent [--longpress] <key code number or name> ... (Default: keyboard)
tap <x> <y> (Default: touchscreen)
swipe <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen)
press (Default: trackball)
roll <dx> <dy> (Default: trackball)

经试验使用roll命令可以实现当前界面上下滑动的效果,其中dx,dy应该是以像素为单位进行滑动,而不是以坐标。值为正向下滑动,为负向上滑动

input roll 10 10

input roll 10 10


这里滚动好像还和是否获取焦点有关,不做任何点击则滚动如上图,若点击数据0后,执行命令

如果点击的是数据5,则

想要滚动到底,则

inmut roll 20000 20000

输入一个大于要移动的像素值

直接从0滚到19999,当然这种操作会有点卡顿。

1
2
3
4
5
6
7
8
9
10
11
12
13
Toast.makeText(this, "3秒后滑动到底部", Toast.LENGTH_SHORT).show();
final int mX=20000;
final int mY=20000;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
try {
Runtime.getRuntime().exec("input roll " + mX + " " + mY);
} catch (IOException e) {
e.printStackTrace();
}
}
}, 3000);

问题:如果是自己应用界面滑动是没有问题的,但如果是想要滑动其他界面那这种方式就行不通了。会报

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
06-20 14:09:31.101 11087-11087/? E/JavaBinder: Unknown binder error code. 0xfffffff7
06-20 14:09:31.101 11087-11087/? E/ServiceManager: error in getService
android.os.RemoteException: Unknown binder error code. 0xfffffff7
at android.os.BinderProxy.transact(Native Method)
at android.os.ServiceManagerProxy.getService(ServiceManagerNative.java:123)
at android.os.ServiceManager.getService(ServiceManager.java:55)
at android.app.ActivityManagerNative$1.create(ActivityManagerNative.java:2042)
at android.app.ActivityManagerNative$1.create(ActivityManagerNative.java:2040)
at android.util.Singleton.get(Singleton.java:34)
at android.app.ActivityManagerNative.getDefault(ActivityManagerNative.java:76)
at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:86)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690)
at dalvik.system.NativeStart.main(Native Method)
06-20 14:09:31.101 11087-11087/? I/Process: Sending signal. PID: 11087 SIG: 9
06-20 14:09:31.101 11087-11087/? E/AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main
java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
at android.os.Parcel.readException(Parcel.java:1465)
at android.os.Parcel.readException(Parcel.java:1419)
at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:356)
at android.hardware.input.InputManager.injectInputEvent(InputManager.java:641)
at com.android.commands.input.Input.injectMotionEvent(Input.java:259)
at com.android.commands.input.Input.sendMove(Input.java:228)
at com.android.commands.input.Input.run(Input.java:130)
at com.android.commands.input.Input.main(Input.java:59)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:258)
at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:135)
at dalvik.system.NativeStart.main(Native Method)
06-20 14:09:31.101 11087-11087/? E/AndroidRuntime: Error reporting crash
java.lang.NullPointerException
at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:86)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690)
at dalvik.system.NativeStart.main(Native Method)

就是需要 INJECT_EVENTS permission 权限,而这个权限又需要系统的签名才行。网上的解决办法

  • 需要系统签名我就给你签名好了,添加 sharedUserIdandroid.permission.INJECT_EVENTS
    1
    2
    3
    4
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:sharedUserId="android.uid.system"
    package="com.example.administrator.myapplication">
    <uses-permission android:name="android.permission.INJECT_EVENTS" />

然后使用platform.x509.pem platform.pk8 signapk.jar 将程序签名为系统程序。
这里我试试没有成功,不知道是不是下载的文件不对。

难道adb一行命令的事,非要做这么多操作吗?
还是有办法的,那就是先获取root权限后再执行滑动操作

1
2
3
4
5
6
7
8
9
OutputStream os = Runtime.getRuntime().exec("su").getOutputStream();
int code1 = 30;
int code2 = 30;
String cmd ="input roll " + code1+ " "+code2+ "\n";
os.write(cmd.getBytes());
os.flush();
} catch (IOException e) {
e.printStackTrace();
}

不过有一个小小的问题就是这个操作会存在一定的延时