├── .gitignore
├── Demo
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── silence
│ │ └── com
│ │ └── cn
│ │ └── a310application
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── tcpdump
│ ├── java
│ │ └── silence
│ │ │ └── com
│ │ │ └── cn
│ │ │ └── a310application
│ │ │ ├── Common.java
│ │ │ ├── MyApplication.java
│ │ │ ├── activity
│ │ │ └── MainActivity.java
│ │ │ ├── appanalyst
│ │ │ ├── AnalystApplication.java
│ │ │ ├── AnalystBusiness.java
│ │ │ ├── AnalystService.java
│ │ │ ├── AnalystServiceHandler.java
│ │ │ ├── CheckFormResult.java
│ │ │ ├── DescriptionMatcher.java
│ │ │ ├── DescriptionMatcherFactory.java
│ │ │ ├── FormControl.java
│ │ │ ├── FormTree.java
│ │ │ ├── Gps.java
│ │ │ ├── GpsMocker.java
│ │ │ ├── Log.java
│ │ │ ├── Macro.java
│ │ │ ├── MyException.java
│ │ │ ├── MyProcess.java
│ │ │ ├── PointXY.java
│ │ │ ├── PointXY2XY.java
│ │ │ ├── StringUtil.java
│ │ │ ├── TextMatcher.java
│ │ │ ├── TextMatcherFactory.java
│ │ │ ├── ToastDuration.java
│ │ │ ├── UiNode.java
│ │ │ └── UiTree.java
│ │ │ ├── qqanalyst
│ │ │ ├── FormPage.java
│ │ │ ├── QQApplication.java
│ │ │ └── QQSimulateBusiness.java
│ │ │ └── util
│ │ │ ├── BashHelper.java
│ │ │ ├── FilterFile.java
│ │ │ ├── SilenceLog.java
│ │ │ └── Util.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── wxa_service.xml
│ └── test
│ └── java
│ └── silence
│ └── com
│ └── cn
│ └── a310application
│ └── ExampleUnitTest.java
├── LICENSE
├── README.md
└── libs
└── appanalyst
├── AnalystApplication.java
├── AnalystBusiness.java
├── AnalystService.java
├── AnalystServiceHandler.java
├── CheckFormResult.java
├── DescriptionMatcher.java
├── DescriptionMatcherFactory.java
├── FormControl.java
├── FormTree.java
├── Gps.java
├── GpsMocker.java
├── Log.java
├── Macro.java
├── MyException.java
├── MyProcess.java
├── PointXY.java
├── PointXY2XY.java
├── StringUtil.java
├── TextMatcher.java
├── TextMatcherFactory.java
├── ToastDuration.java
├── UiNode.java
└── UiTree.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/workspace.xml
38 | .idea/tasks.xml
39 | .idea/gradle.xml
40 | .idea/dictionaries
41 | .idea/libraries
42 |
43 | # Keystore files
44 | *.jks
45 |
46 | # External native build folder generated in Android Studio 2.2 and later
47 | .externalNativeBuild
48 |
49 | # Google Services (e.g. APIs or Firebase)
50 | google-services.json
51 |
52 | # Freeline
53 | freeline.py
54 | freeline/
55 | freeline_project_description.json
56 |
--------------------------------------------------------------------------------
/Demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.3"
6 | defaultConfig {
7 | applicationId "silence.com.cn.a310application"
8 | minSdkVersion 19
9 | targetSdkVersion 21
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:24.2.1'
28 | testCompile 'junit:junit:4.12'
29 | }
30 |
--------------------------------------------------------------------------------
/Demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\android-sdk_r24.4.1-windows\android-sdk-windows/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/Demo/src/androidTest/java/silence/com/cn/a310application/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("silence.com.cn.a310application", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Demo/src/main/assets/tcpdump:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littleRich/AutoInteraction-Library/b27c7e26e4a65c1c7edbea8524e0be845ef68660/Demo/src/main/assets/tcpdump
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/Common.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application;
2 |
3 | /**
4 | * Created by zhukun on 2016/10/27 0027.
5 | * 常量值定义
6 | */
7 |
8 | public class Common {
9 | }
10 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/MyApplication.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * Created by zhukun on 2016/10/27 0027.
7 | */
8 |
9 | public class MyApplication extends Application {
10 | }
11 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.activity;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import android.view.View;
7 |
8 | import java.io.FileReader;
9 |
10 | import silence.com.cn.a310application.R;
11 | import silence.com.cn.a310application.util.BashHelper;
12 | import silence.com.cn.a310application.util.FilterFile;
13 |
14 | public class MainActivity extends Activity {
15 |
16 | private static final String TAG = "MainActivity";
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_main);
22 |
23 | findViewById(R.id.btn_start_capture).setOnClickListener(new View.OnClickListener() {
24 | @Override
25 | public void onClick(View view) {
26 | Log.d(TAG, "抓包数据文件:" + BashHelper.DEST_FILE);
27 | new Thread(new Runnable() {
28 | @Override
29 | public void run() {
30 | final boolean retVal = BashHelper.startCapture(MainActivity.this);
31 | Log.d(TAG, "抓包状态:" + retVal);
32 | }
33 | }).start();
34 | }
35 | });
36 |
37 | findViewById(R.id.btn_stop_capture).setOnClickListener(new View.OnClickListener() {
38 | @Override
39 | public void onClick(View view) {
40 |
41 | new Thread(new Runnable() {
42 | @Override
43 | public void run() {
44 | FilterFile.extractQQ(null);
45 | BashHelper.stopCapture(MainActivity.this);
46 | }
47 | }).start();
48 | }
49 | });
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/AnalystApplication.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.app.Application;
4 | import android.support.annotation.NonNull;
5 |
6 | public abstract class AnalystApplication extends Application {
7 | @Override
8 | public void onCreate() {
9 | super.onCreate();
10 | Log.init(getAppName());
11 | Log.open();
12 | }
13 |
14 | @Override
15 | public void onTerminate() {
16 | super.onTerminate();
17 | Log.close();
18 | }
19 |
20 | @NonNull
21 | public abstract String getAppName();
22 | }
23 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/AnalystBusiness.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | public abstract class AnalystBusiness {
6 | private AnalystServiceHandler mServiceHandler;
7 |
8 | protected AnalystBusiness(@NonNull AnalystServiceHandler serviceHandler) {
9 | if (null == serviceHandler) {
10 | throw new NullPointerException("serviceHandler");
11 | }
12 | mServiceHandler = serviceHandler;
13 | }
14 |
15 | protected AnalystServiceHandler getServiceHandler() {
16 | return mServiceHandler;
17 | }
18 |
19 | public abstract void handleUiEvent(@NonNull UiTree uiTree, boolean keepingAlive) throws MyException;
20 | }
21 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/AnalystService.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.accessibilityservice.AccessibilityService;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Handler;
7 | import android.os.Message;
8 | import android.support.annotation.NonNull;
9 | import android.view.accessibility.AccessibilityEvent;
10 |
11 | import java.lang.reflect.Constructor;
12 |
13 | public class AnalystService extends AccessibilityService {
14 | public static final int ID_POST_EVENT = 1;
15 | public static final int ID_POST_KEEP_ALIVE = 2;
16 |
17 | public static final int DELAY_POST_EVENT = 1000;
18 | public static final int DELAY_KEEP_ALIVE = 15* 1000;
19 |
20 | private static Constructor extends AnalystBusiness> mBusinessClazzConstructor;
21 | private static MyProcess mSuProcess;
22 |
23 | private final Handler mMainHandler = new Handler(new Handler.Callback() {
24 | @Override
25 | public boolean handleMessage(Message msg) {
26 | ((Runnable) msg.obj).run();
27 | return true;
28 | }
29 | });
30 | private final GpsMocker mGpsMocker = new GpsMocker();
31 |
32 | private AnalystBusiness mBusiness;
33 | private String mLastEventClassName;
34 | private int mLastPostingCount;
35 | private int mNullChildAccessibilityNodeInfoExceptionCount;
36 | private int mUnknownUiCount;
37 | private int mKeepAliveCount;
38 |
39 | private boolean mRunning = false;
40 |
41 | public class ServiceHandler implements AnalystServiceHandler {
42 | @Override
43 | public Context getContext() {
44 | return AnalystService.this;
45 | }
46 |
47 | @Override
48 | public GpsMocker getGpsMocker() {
49 | return mGpsMocker;
50 | }
51 |
52 | @Override
53 | public void requirePost() {
54 | if (!mRunning) {
55 | return;
56 | }
57 | postEvent();
58 | }
59 |
60 | @Override
61 | public void requireBack() {
62 | if (!mRunning) {
63 | return;
64 | }
65 | performGlobalAction(GLOBAL_ACTION_BACK);
66 | }
67 |
68 | @Override
69 | public void requireMockClick(PointXY point) {
70 | if (!mRunning) {
71 | return;
72 | }
73 | if (null == point) {
74 | return;
75 | }
76 | try {
77 | mSuProcess.exec("input tap " + point.mX + " " + point.mY);
78 | } catch (MyException e) {
79 | Log.e(new MyException("执行requireMockClick异常", e), AnalystService.this, ToastDuration.LENGTH_SHORT);
80 | }
81 | }
82 |
83 | @Override
84 | public void requireMockLongClick(PointXY point) {
85 | if (!mRunning) {
86 | return;
87 | }
88 | if (null == point) {
89 | return;
90 | }
91 | try {
92 | mSuProcess.exec("input swipe " + point.mX + " " + point.mY + " " + point.mX + " " + point.mY + " 1200");
93 | } catch (MyException e) {
94 | Log.e(new MyException("执行requireMockLongClick异常", e), AnalystService.this, ToastDuration.LENGTH_SHORT);
95 | }
96 | }
97 |
98 | @Override
99 | public void requireMockMove(PointXY2XY pointXY2XY) {
100 | if (!mRunning) {
101 | return;
102 | }
103 | if (null == pointXY2XY) {
104 | return;
105 | }
106 | try {
107 | mSuProcess.exec("input swipe " + pointXY2XY.mFrom.mX + " " + pointXY2XY.mFrom.mY + " " + pointXY2XY
108 | .mTo.mX + " " + pointXY2XY.mTo.mY + " 800");
109 | } catch (MyException e) {
110 | Log.e(new MyException("执行requireMockMove异常", e), AnalystService.this, ToastDuration.LENGTH_SHORT);
111 | }
112 | }
113 | }
114 |
115 | public static void init(@NonNull Class extends AnalystBusiness> businessClazz) throws MyException {
116 | try {
117 | mSuProcess = new MyProcess("su");
118 | } catch (Exception e) {
119 | throw new MyException("获取ROOT权限失败", e);
120 | }
121 | try {
122 | mBusinessClazzConstructor = businessClazz.getConstructor(AnalystServiceHandler.class);
123 | } catch (Exception e) {
124 | throw new MyException(e);
125 | }
126 | }
127 |
128 | @Override
129 | public void onCreate() {
130 | mGpsMocker.init(this);
131 | //SilenceLog.e("onCreate");
132 | }
133 |
134 | @Override
135 | protected void onServiceConnected() {
136 | //SilenceLog.e("onServiceConnected");
137 | Log.i("AnalystService.onServiceConnected");
138 |
139 | try {
140 | mBusiness = mBusinessClazzConstructor.newInstance(new ServiceHandler());
141 | } catch (Exception e) {
142 | throw new RuntimeException(e);
143 | }
144 | mLastEventClassName = null;
145 | mLastPostingCount = 0;
146 | mNullChildAccessibilityNodeInfoExceptionCount = 0;
147 | mUnknownUiCount = 0;
148 | mKeepAliveCount = 0;
149 | mRunning = true;
150 | // WatcherUtil.post2watcher(QQApplication.getContext(), "start");
151 | // WatcherUtil.startAlarm(QQApplication.getContext(), true);
152 | }
153 |
154 |
155 | @Override
156 | public void onInterrupt() {
157 | Log.i("AnalystService.onInterrupt");
158 | mRunning = false;
159 | mMainHandler.removeMessages(ID_POST_EVENT);
160 | mMainHandler.removeMessages(ID_POST_KEEP_ALIVE);
161 | mBusiness = null;
162 | // post2watcher("end");
163 | // SilenceLog.e("中断了==============");
164 | }
165 |
166 | @Override
167 | public boolean onUnbind(Intent intent) {
168 | // SilenceLog.e("onUnbind");
169 | // WatcherUtil.post2watcher(QQApplication.getContext(), "end");
170 | // WatcherUtil.startAlarm(QQApplication.getContext(), false);
171 | return super.onUnbind(intent);
172 | }
173 |
174 |
175 | @Override
176 | public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
177 | if (null == accessibilityEvent) {
178 | return;
179 | }
180 | switch (accessibilityEvent.getEventType()) {
181 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
182 | Log.i("AnalystService.onAccessibilityEvent # AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED");
183 | mLastEventClassName = StringUtil.toNb(accessibilityEvent.getClassName());
184 | postEvent();
185 | }
186 | break;
187 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
188 | Log.i("AnalystService.onAccessibilityEvent # AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED");
189 | if (null != mLastEventClassName) {
190 | postEvent();
191 | }
192 | }
193 | break;
194 | }
195 | }
196 |
197 | private void postEvent() {
198 | postEvent(false);
199 | }
200 |
201 |
202 | private void postEvent(final boolean keepingAlive) {
203 | if (!mRunning) {
204 | return;
205 | }
206 | if (keepingAlive) {
207 | mKeepAliveCount++;
208 | if (mKeepAliveCount > 3) {
209 | // WatcherUtil.post2watcher(QQApplication.getContext(), "KillWeChat");
210 | return;
211 | }
212 | } else {
213 | mKeepAliveCount = 0;
214 | }
215 | // WatcherUtil.post2watcher(QQApplication.getContext(), "postEvent");
216 | mMainHandler.removeMessages(ID_POST_EVENT);
217 | Message message = mMainHandler.obtainMessage(ID_POST_EVENT, new Runnable() {
218 | @Override
219 | public void run() {
220 | mLastPostingCount = 0;
221 | UiTree uiTree = null;
222 | try {
223 | uiTree = new UiTree(mLastEventClassName, getRootInActiveWindow());
224 | Log.i(uiTree.getLog());
225 | if (null == uiTree.getFoundForm()) {
226 | Log.i("UI类型: 未知");
227 | mUnknownUiCount++;
228 | if (mUnknownUiCount > 15) {
229 | mUnknownUiCount = 0;
230 | // WatcherUtil.post2watcher(QQApplication.getContext(), "KillWeChat");
231 | Log.e("未知页面次数太多,返回", AnalystService.this, ToastDuration.LENGTH_SHORT);
232 | performGlobalAction(GLOBAL_ACTION_BACK);
233 | }
234 | } else {
235 | // WatcherUtil.post2watcher(QQApplication.getContext(), "PageChange");
236 | String formPage = uiTree.getFoundFormPage();
237 | Log.i("UI类型: " + formPage);
238 | mUnknownUiCount = 0;
239 | mBusiness.handleUiEvent(uiTree, keepingAlive);
240 | }
241 | mNullChildAccessibilityNodeInfoExceptionCount = 0;
242 | } catch (UiNode.NullChildAccessibilityNodeInfoException e) {
243 | Log.e(new MyException("AnalystService.postEvent # run", e));
244 | mNullChildAccessibilityNodeInfoExceptionCount++;
245 | if (mNullChildAccessibilityNodeInfoExceptionCount > 5) {
246 | Log.e(e, AnalystService.this, ToastDuration.LENGTH_SHORT);
247 | // WatcherUtil.post2watcher(QQApplication.getContext(), "AccessibilityNodeInfo");
248 | } else {
249 | postEvent();
250 | }
251 | } catch (Exception e) {
252 | Log.e(new MyException("AnalystService.postEvent # run", e));
253 | mNullChildAccessibilityNodeInfoExceptionCount = 0;
254 | } finally {
255 | if (null != uiTree) {
256 | uiTree.recycle();
257 | }
258 | }
259 | }
260 | });
261 | mLastPostingCount++;
262 | if (keepingAlive || mLastPostingCount > 20) {
263 | // KeepingAlive的时候,立马执行
264 | // 连续post次数过多,则不再延迟,立马执行
265 | mMainHandler.sendMessage(message);
266 | } else {
267 | mMainHandler.sendMessageDelayed(message, DELAY_POST_EVENT);
268 | }
269 | postKeepAlive();
270 | }
271 |
272 | private void postKeepAlive() {
273 | if (!mRunning) {
274 | return;
275 | }
276 | mMainHandler.removeMessages(ID_POST_KEEP_ALIVE);
277 | Message message = mMainHandler.obtainMessage(ID_POST_KEEP_ALIVE, new Runnable() {
278 | @Override
279 | public void run() {
280 | postEvent(true);
281 | }
282 | });
283 | mMainHandler.sendMessageDelayed(message, DELAY_KEEP_ALIVE);
284 | }
285 |
286 |
287 | }
288 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/AnalystServiceHandler.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.content.Context;
4 |
5 | public interface AnalystServiceHandler {
6 | Context getContext();
7 |
8 | GpsMocker getGpsMocker();
9 |
10 | void requirePost();
11 |
12 | void requireBack();
13 |
14 | void requireMockClick(PointXY point);
15 |
16 | void requireMockLongClick(PointXY point);
17 |
18 | void requireMockMove(PointXY2XY pointXY2XY);
19 | }
20 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/CheckFormResult.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class CheckFormResult extends Exception {
4 | private int[] mPath;
5 |
6 | public CheckFormResult(int[] path, String message) {
7 | super(message);
8 | mPath = path;
9 | }
10 |
11 | public int[] getPath() {
12 | return mPath;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/DescriptionMatcher.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | public class DescriptionMatcher extends DescriptionMatcherFactory {
6 | public final String mDescription;
7 |
8 | public DescriptionMatcher(@Nullable String description) {
9 | if (null == description) {
10 | throw new NullPointerException("description");
11 | }
12 | mDescription = description;
13 | }
14 |
15 | @Override
16 | public boolean onMatch(@Nullable String description) {
17 | return StringUtil.compareNb(mDescription, description);
18 | }
19 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/DescriptionMatcherFactory.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | public abstract class DescriptionMatcherFactory {
6 | public abstract boolean onMatch(@Nullable String description);
7 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/FormControl.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | import java.util.ArrayList;
7 | import java.util.HashSet;
8 | import java.util.Set;
9 |
10 | public class FormControl {
11 | private final String mClassName;
12 | private final TextMatcherFactory mTextMatcher;
13 | private final DescriptionMatcherFactory mDescriptionMatcher;
14 | private final ArrayList mChilds = new ArrayList<>();
15 | private boolean mEssential = true;
16 | private final Set mIncludeTexts = new HashSet<>();
17 | private final Set mExcludeTexts = new HashSet<>();
18 |
19 | public FormControl(@Nullable String className) {
20 | this(className, null, null);
21 | }
22 |
23 | public FormControl(@Nullable String className, @Nullable TextMatcherFactory textMatcher) {
24 | this(className, textMatcher, null);
25 | }
26 |
27 | public FormControl(@Nullable String className, @Nullable DescriptionMatcherFactory descriptionMatcher) {
28 | this(className, null, descriptionMatcher);
29 | }
30 |
31 | public FormControl(@Nullable String className, @Nullable TextMatcherFactory textMatcher, @Nullable DescriptionMatcherFactory descriptionMatcher) {
32 | mClassName = className;
33 | mTextMatcher = textMatcher;
34 | mDescriptionMatcher = descriptionMatcher;
35 | }
36 |
37 | public FormControl set(int index, @NonNull FormControl child) {
38 | set(index, true, child);
39 | return this;
40 | }
41 |
42 | public FormControl set(int index, boolean essential, @NonNull FormControl child) {
43 | if (index < 0) {
44 | throw new IndexOutOfBoundsException("index=" + index);
45 | }
46 | if (index > 20) {
47 | throw new IndexOutOfBoundsException("index>20; index=" + index);
48 | }
49 | if (null == child) {
50 | throw new NullPointerException("child");
51 | }
52 | if (index < mChilds.size()) {
53 | throw new ArrayStoreException();
54 | }
55 | child.mEssential = essential;
56 | for (int i = mChilds.size(); i < index + 1; i++) {
57 | mChilds.add(null);
58 | }
59 | mChilds.set(index, child);
60 | return this;
61 | }
62 |
63 | public FormControl includeText(@NonNull String text) {
64 | if (null == text) {
65 | throw new NullPointerException("text");
66 | }
67 | if (Macro.Debug) {
68 | if (mIncludeTexts.contains(text)) {
69 | throw new ArrayStoreException("代码错误");
70 | }
71 | if (mExcludeTexts.contains(text)) {
72 | throw new ArrayStoreException("代码错误");
73 | }
74 | }
75 | mIncludeTexts.add(text);
76 | return this;
77 | }
78 |
79 | public FormControl excludeText(@NonNull String text) {
80 | if (null == text) {
81 | throw new NullPointerException("text");
82 | }
83 | if (Macro.Debug) {
84 | if (mIncludeTexts.contains(text)) {
85 | throw new ArrayStoreException("代码错误");
86 | }
87 | if (mExcludeTexts.contains(text)) {
88 | throw new ArrayStoreException("代码错误");
89 | }
90 | }
91 | mExcludeTexts.add(text);
92 | return this;
93 | }
94 |
95 | public void check(@NonNull UiNode uiNode) throws CheckFormResult {
96 | if (null == uiNode) {
97 | throw new NullPointerException("uiNode");
98 | }
99 | uiNode.setFormIndex(0);
100 | check(new int[]{0}, uiNode);
101 | }
102 |
103 | private void check(@NonNull int[] path, @NonNull UiNode uiNode) throws CheckFormResult {
104 | if (null == path) {
105 | throw new NullPointerException("path");
106 | }
107 | if (null == uiNode) {
108 | throw new NullPointerException("uiNode");
109 | }
110 | if (null == mClassName) {
111 | throw new CheckFormResult(path, "当前不应该存在控件");
112 | }
113 | if (!mClassName.equals(uiNode.getClassName())) {
114 | throw new CheckFormResult(path, "类型不匹配");
115 | }
116 | if (null != mTextMatcher) {
117 | if (!mTextMatcher.onMatch(uiNode.getText())) {
118 | throw new CheckFormResult(path, "文本不匹配");
119 | }
120 | }
121 | if (null != mDescriptionMatcher) {
122 | if (!mDescriptionMatcher.onMatch(uiNode.getDescription())) {
123 | throw new CheckFormResult(path, "描述不匹配");
124 | }
125 | }
126 | for (String text : mIncludeTexts) {
127 | if (uiNode.findChildsByText(text).isEmpty()) {
128 | throw new CheckFormResult(path, "没有找到必须的文本");
129 | }
130 | }
131 | for (String text : mExcludeTexts) {
132 | if (!uiNode.findChildsByText(text).isEmpty()) {
133 | throw new CheckFormResult(path, "含有需要排除的文本");
134 | }
135 | }
136 | int realIndex = 0;
137 | for (int i = 0; i < mChilds.size(); i++) {
138 | FormControl childControl = mChilds.get(i);
139 | if (null == childControl) {
140 | realIndex++;
141 | } else {
142 | int[] childPath = new int[path.length + 1];
143 | System.arraycopy(path, 0, childPath, 0, path.length);
144 | childPath[path.length] = i;
145 | if (realIndex >= uiNode.getChildCount()) {
146 | if (null != childControl.mClassName && childControl.mEssential) {
147 | throw new CheckFormResult(childPath, "未找到结点");
148 | }
149 | } else {
150 | UiNode childUiNode = uiNode.getChild(realIndex);
151 | try {
152 | childControl.check(childPath, childUiNode);
153 | childUiNode.setFormIndex(i);
154 | realIndex++;
155 | } catch (CheckFormResult e) {
156 | if (childControl.mEssential) {
157 | throw e;
158 | }
159 | }
160 | }
161 | }
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/FormTree.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | public class FormTree {
10 | private final String mFormPage;
11 | private final String mClassName;
12 | private final FormControl mControl;
13 | private final Map mControlPaths = new HashMap<>();
14 |
15 | public FormTree(@NonNull String formPage, @NonNull String className, @NonNull FormControl control) {
16 | if (null == formPage) {
17 | throw new NullPointerException("formPage");
18 | }
19 | if (null == className) {
20 | throw new NullPointerException("className");
21 | }
22 | if (null == control) {
23 | throw new NullPointerException("control");
24 | }
25 | mFormPage = formPage;
26 | mClassName = className;
27 | mControl = control;
28 | }
29 |
30 | @NonNull
31 | public String getClassName() {
32 | return mClassName;
33 | }
34 |
35 | @NonNull
36 | public String getFormPage() {
37 | return mFormPage;
38 | }
39 |
40 | @NonNull
41 | public FormControl getControl() {
42 | return mControl;
43 | }
44 |
45 | public void addControlPath(@NonNull String pathName, @NonNull int[] path) {
46 | if (null == pathName) {
47 | throw new NullPointerException("pathName");
48 | }
49 | if (null == path) {
50 | throw new NullPointerException("path");
51 | }
52 | mControlPaths.put(pathName, path);
53 | }
54 |
55 | @Nullable
56 | public int[] getControlPath(@NonNull String pathName) {
57 | if (null == pathName) {
58 | throw new NullPointerException("pathName");
59 | }
60 | return mControlPaths.get(pathName);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/Gps.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class Gps {
4 | public float mLatitude;
5 | public float mLongitude;
6 |
7 | public Gps(float latitude, float longitude) {
8 | mLatitude = latitude;
9 | mLongitude = longitude;
10 | }
11 |
12 | public Gps(Gps gps) {
13 | mLatitude = gps.mLatitude;
14 | mLongitude = gps.mLongitude;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/GpsMocker.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.content.Context;
4 | import android.location.Location;
5 | import android.location.LocationListener;
6 | import android.location.LocationManager;
7 | import android.location.LocationProvider;
8 | import android.os.Bundle;
9 | import android.os.Handler;
10 | import android.os.SystemClock;
11 | import android.support.annotation.NonNull;
12 |
13 | public class GpsMocker {
14 | private Gps[] mMockGps = new Gps[]{null};
15 |
16 | public void init(@NonNull Context context) {
17 | if (null == context) {
18 | throw new NullPointerException("context");
19 | }
20 | if (Macro.RealGps) {
21 | return;
22 | }
23 | LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
24 | locationManager.addTestProvider(LocationManager.GPS_PROVIDER,
25 | false, true, true, false, true, true, true,
26 | android.location.Criteria.POWER_HIGH,
27 | android.location.Criteria.ACCURACY_FINE);
28 | locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
29 | locationManager.setTestProviderStatus(LocationManager.GPS_PROVIDER, LocationProvider.AVAILABLE, null, System.currentTimeMillis());
30 | locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, new LocationListener() {
31 | @Override
32 | public void onLocationChanged(Location location) {
33 | }
34 |
35 | @Override
36 | public void onStatusChanged(String provider, int status, Bundle extras) {
37 | }
38 |
39 | @Override
40 | public void onProviderEnabled(String provider) {
41 | }
42 |
43 | @Override
44 | public void onProviderDisabled(String provider) {
45 | }
46 | }
47 | );
48 | }
49 |
50 | public void start(@NonNull Context context, @NonNull Gps gps) {
51 | synchronized (mMockGps) {
52 | mMockGps[0] = gps;
53 | }
54 | scheduleMockGps(context);
55 | }
56 |
57 | public boolean isWorking() {
58 | synchronized (mMockGps) {
59 | return null != mMockGps[0];
60 | }
61 | }
62 |
63 | private void scheduleMockGps(final Context context) {
64 | Gps gps;
65 | synchronized (mMockGps) {
66 | gps = mMockGps[0];
67 | }
68 | if (null == gps) {
69 | return;
70 | }
71 | if (!Macro.RealGps) {
72 | LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
73 | Location location = new Location(LocationManager.GPS_PROVIDER);
74 | location.setLatitude(gps.mLatitude);
75 | location.setLongitude(gps.mLongitude);
76 | location.setAltitude(0);
77 | location.setBearing(0);
78 | location.setSpeed(0);
79 | location.setAccuracy(2);
80 | location.setTime(System.currentTimeMillis());
81 | location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
82 | locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, location);
83 | }
84 | new Handler(context.getMainLooper()).postDelayed(new Runnable() {
85 | @Override
86 | public void run() {
87 | scheduleMockGps(context);
88 | }
89 | }, 1000);
90 | }
91 |
92 | public void stop() {
93 | synchronized (mMockGps) {
94 | mMockGps[0] = null;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/Log.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import android.support.annotation.NonNull;
8 | import android.support.annotation.Nullable;
9 | import android.widget.Toast;
10 |
11 | import java.io.File;
12 | import java.io.FileOutputStream;
13 | import java.io.IOException;
14 | import java.io.PrintWriter;
15 | import java.io.StringWriter;
16 | import java.text.SimpleDateFormat;
17 | import java.util.Date;
18 |
19 | public class Log {
20 | private static String mAppName;
21 | private static FileOutputStream mOuputStream;
22 |
23 | private interface LogOutput {
24 | void output(String tag, String message);
25 | }
26 |
27 | public static void init(String appName) {
28 | if (null == appName) {
29 | throw new NullPointerException("appName");
30 | }
31 | mAppName = appName;
32 | }
33 |
34 | public static void open() {
35 | if (null == mAppName) {
36 | throw new NullPointerException("mAppName");
37 | }
38 | if (null != mOuputStream) {
39 | try {
40 | mOuputStream.close();
41 | } catch (IOException e) {
42 | e.printStackTrace();
43 | }
44 | mOuputStream = null;
45 | }
46 | File file = null;
47 | for (int i = 0; i < 5; i++) {
48 | file = new File(Environment.getExternalStorageDirectory(), mAppName + "/logs/" + new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + ".txt");
49 | if (!file.exists()) {
50 | break;
51 | }
52 | }
53 | if (null != file && !file.exists()) {
54 | try {
55 | file.getParentFile().mkdirs();
56 | file.createNewFile();
57 | mOuputStream = new FileOutputStream(file);
58 | } catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 | }
62 | }
63 |
64 | public static void close() {
65 | if (null != mOuputStream) {
66 | try {
67 | mOuputStream.close();
68 | } catch (IOException e) {
69 | e.printStackTrace();
70 | }
71 | mOuputStream = null;
72 | }
73 | }
74 |
75 | public static void d(@NonNull String message) {
76 | d(message, null, null);
77 | }
78 |
79 | public static void i(@NonNull String message) {
80 | i(message, null, null);
81 | }
82 |
83 | public static void w(@NonNull String message) {
84 | w(message, null, null);
85 | }
86 |
87 | public static void e(@NonNull String message) {
88 | e(message, null, null);
89 | }
90 |
91 | public static void e(@NonNull Throwable throwable) {
92 | e(throwable, null, null);
93 | }
94 |
95 | public static void d(@NonNull String message, Context context, @Nullable ToastDuration toastDuration) {
96 | outputLog(new LogOutput() {
97 | @Override
98 | public void output(String tag, String message) {
99 | android.util.Log.d(tag, message);
100 | }
101 | }, message);
102 | outputFileLog("调试", message);
103 | if (null != toastDuration) {
104 | showToast(message, context, toastDuration.value());
105 | }
106 | }
107 |
108 | public static void i(@NonNull String message, Context context, @Nullable ToastDuration toastDuration) {
109 | outputLog(new LogOutput() {
110 | @Override
111 | public void output(String tag, String message) {
112 | android.util.Log.i(tag, message);
113 | }
114 | }, message);
115 | outputFileLog("信息", message);
116 | if (null != toastDuration) {
117 | showToast(message, context, toastDuration.value());
118 | }
119 | }
120 |
121 | public static void w(@NonNull String message, Context context, @Nullable ToastDuration toastDuration) {
122 | outputLog(new LogOutput() {
123 | @Override
124 | public void output(String tag, String message) {
125 | android.util.Log.w(tag, message);
126 | }
127 | }, message);
128 | outputFileLog("警告", message);
129 | if (null != toastDuration) {
130 | showToast(message, context, toastDuration.value());
131 | }
132 | }
133 |
134 | public static void e(@NonNull String message, Context context, @Nullable ToastDuration toastDuration) {
135 | outputLog(new LogOutput() {
136 | @Override
137 | public void output(String tag, String message) {
138 | android.util.Log.e(tag, message);
139 | }
140 | }, message);
141 | outputFileLog("错误", message);
142 | if (null != toastDuration) {
143 | showToast(message, context, toastDuration.value());
144 | }
145 | }
146 |
147 | public static void e(@NonNull Throwable throwable, Context context, @Nullable ToastDuration toastDuration) {
148 | StringWriter writer = new StringWriter();
149 | PrintWriter printWriter = new PrintWriter(writer);
150 | throwable.printStackTrace(printWriter);
151 | String text = writer.toString();
152 | outputLog(new LogOutput() {
153 | @Override
154 | public void output(String tag, String message) {
155 | android.util.Log.e(tag, message);
156 | }
157 | }, text);
158 | outputFileLog("错误", text);
159 | if (null != toastDuration) {
160 | String message = throwable.getMessage();
161 | if (null != message) {
162 | showToast(message, context, toastDuration.value());
163 | }
164 | }
165 | }
166 |
167 | private static void outputLog(@NonNull LogOutput logOutput, @NonNull String message) {
168 | if (null == logOutput) {
169 | throw new NullPointerException("logOutput");
170 | }
171 | if (null == message) {
172 | throw new NullPointerException("message");
173 | }
174 | if (null == mAppName) {
175 | throw new NullPointerException("mAppName");
176 | }
177 | String texts[] = message.split("\\n");
178 | String log = "";
179 | int n = 0;
180 | for (String text : texts) {
181 | if ("".equals(log)) {
182 | log = text;
183 | } else {
184 | if (log.length() + text.length() > 3800) {
185 | logOutput.output(mAppName, log);
186 | n++;
187 | log = "第" + (n + 1) + "部分: \n" + text;
188 | } else {
189 | if (!"".equals(log)) {
190 | log += "\n";
191 | }
192 | log += text;
193 | }
194 | }
195 | }
196 | logOutput.output(mAppName, log);
197 | }
198 |
199 | private static void outputFileLog(@NonNull String type, @NonNull String message) {
200 | if (null == type) {
201 | throw new NullPointerException("type");
202 | }
203 | if (null == message) {
204 | throw new NullPointerException("message");
205 | }
206 | if (null != mOuputStream) {
207 | synchronized (mOuputStream) {
208 | String text = new SimpleDateFormat("yyyyMMdd,HHmmss,SSS").format(new Date()) + "|" + type + ": " + message + "\n";
209 | try {
210 | mOuputStream.write(text.getBytes());
211 | mOuputStream.flush();
212 | } catch (IOException e) {
213 | e.printStackTrace();
214 | }
215 | }
216 | }
217 | }
218 |
219 | private static void showToast(@NonNull final String message, final Context context, final int duration) {
220 | if (null == message) {
221 | throw new NullPointerException("message");
222 | }
223 | if (null == mAppName) {
224 | throw new NullPointerException("mAppName");
225 | }
226 | if (Looper.myLooper() == context.getMainLooper()) {
227 | Toast.makeText(context, mAppName + ": " + message, duration).show();
228 | } else {
229 | new Handler(context.getMainLooper()).post(new Runnable() {
230 | @Override
231 | public void run() {
232 | Toast.makeText(context, mAppName + ": " + message, duration).show();
233 | }
234 | });
235 | }
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/Macro.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class Macro {
4 | public static final boolean Debug = true;
5 | public static final boolean Network = false;
6 | public static final boolean RealGps = true;
7 | //public static final Gps MockGps = new Gps(30.252497f, 120.169061f);//杭州
8 | //public static final Gps MockGps = new Gps(30.661398f,104.075244f);//成都
9 | public static final Gps MockGps = new Gps(39.726922f, 116.030136f);//北京郊区
10 | //public static final Gps MockGps = new Gps(31.409741f,90.027551f);//西藏山区
11 | }
12 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/MyException.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class MyException extends Exception {
4 | public MyException() {
5 | super();
6 | }
7 |
8 | public MyException(String detailMessage) {
9 | super(detailMessage);
10 | }
11 |
12 | public MyException(String detailMessage, Throwable throwable) {
13 | super(detailMessage, throwable);
14 | }
15 |
16 | public MyException(Throwable throwable) {
17 | super(throwable);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/MyProcess.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import java.io.OutputStream;
4 |
5 | public class MyProcess {
6 | private final Process mProcess;
7 |
8 | public MyProcess(String name) throws MyException {
9 | try {
10 | mProcess = Runtime.getRuntime().exec(name);
11 | } catch (Exception e) {
12 | throw new MyException(e);
13 | }
14 | }
15 |
16 | public void exec(String cmd) throws MyException {
17 | OutputStream outputStream = mProcess.getOutputStream();
18 | try {
19 | outputStream.write((cmd + "\n").getBytes());
20 | outputStream.flush();
21 | } catch (Exception e) {
22 | throw new MyException(e);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/PointXY.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class PointXY {
4 | public int mX;
5 | public int mY;
6 |
7 | public PointXY(int x, int y) {
8 | mX = x;
9 | mY = y;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/PointXY2XY.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class PointXY2XY {
4 | public PointXY mFrom;
5 | public PointXY mTo;
6 |
7 | public PointXY2XY(PointXY from, PointXY to) {
8 | mFrom = from;
9 | mTo = to;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/StringUtil.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | public class StringUtil {
7 | @NonNull
8 | public static String toEmptiable(@Nullable CharSequence text) {
9 | if (null == text) {
10 | return "";
11 | }
12 | return toEmptiable(text.toString());
13 | }
14 |
15 | @NonNull
16 | public static String toEb(@Nullable CharSequence text) {
17 | return toEmptiable(text);
18 | }
19 |
20 | @NonNull
21 | public static String toEmptiable(@Nullable String text) {
22 | if (null == text) {
23 | return "";
24 | }
25 | return text;
26 | }
27 |
28 | @NonNull
29 | public static String toEb(@Nullable String text) {
30 | return toEmptiable(text);
31 | }
32 |
33 | @Nullable
34 | public static String toNullable(@Nullable CharSequence text) {
35 | if (null == text) {
36 | return null;
37 | }
38 | return text.toString();
39 | }
40 |
41 | @Nullable
42 | public static String toNb(@Nullable CharSequence text) {
43 | return toNullable(text);
44 | }
45 |
46 | public static boolean compareEmptiable(@Nullable CharSequence left, @Nullable CharSequence right) {
47 | return compareEmptiable(null == left ? null : left.toString(), null == right ? null : right.toString());
48 | }
49 |
50 | public static boolean compareEb(@Nullable CharSequence left, @Nullable CharSequence right) {
51 | return compareEmptiable(left, right);
52 | }
53 |
54 | public static boolean compareEmptiable(@Nullable String left, @Nullable CharSequence right) {
55 | return compareEmptiable(left, null == right ? null : right.toString());
56 | }
57 |
58 | public static boolean compareEb(@Nullable String left, @Nullable CharSequence right) {
59 | return compareEmptiable(left, right);
60 | }
61 |
62 | public static boolean compareEmptiable(@Nullable CharSequence left, @Nullable String right) {
63 | return compareEmptiable(null == left ? null : left.toString(), right);
64 | }
65 |
66 | public static boolean compareEb(@Nullable CharSequence left, @Nullable String right) {
67 | return compareEmptiable(left, right);
68 | }
69 |
70 | public static boolean compareEmptiable(@Nullable String left, @Nullable String right) {
71 | return toEmptiable(left).equals(toEmptiable(right));
72 | }
73 |
74 | public static boolean compareEb(@Nullable String left, @Nullable String right) {
75 | return compareEmptiable(left, right);
76 | }
77 |
78 | public static boolean compareNullable(@Nullable CharSequence left, @Nullable CharSequence right) {
79 | return compareNullable(null == left ? null : left.toString(), null == right ? null : right.toString());
80 | }
81 |
82 | public static boolean compareNb(@Nullable CharSequence left, @Nullable CharSequence right) {
83 | return compareNullable(left, right);
84 | }
85 |
86 | public static boolean compareNullable(@Nullable String left, @Nullable CharSequence right) {
87 | return compareNullable(left, null == right ? null : right.toString());
88 | }
89 |
90 | public static boolean compareNb(@Nullable String left, @Nullable CharSequence right) {
91 | return compareNullable(left, right);
92 | }
93 |
94 | public static boolean compareNullable(@Nullable CharSequence left, @Nullable String right) {
95 | return compareNullable(null == left ? null : left.toString(), right);
96 | }
97 |
98 | public static boolean compareNullable(@Nullable String left, @Nullable String right) {
99 | if (null == left) {
100 | return null == right;
101 | }
102 | if (null == right) {
103 | return null == left;
104 | }
105 | return left.equals(right);
106 | }
107 |
108 | public static boolean compareNb(@Nullable String left, @Nullable String right) {
109 | return compareNullable(left, right);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/TextMatcher.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | public class TextMatcher extends TextMatcherFactory {
6 | public final String mText;
7 |
8 | public TextMatcher(@Nullable String text) {
9 | mText = text;
10 | }
11 |
12 | @Override
13 | public boolean onMatch(@Nullable String text) {
14 | return StringUtil.compareNb(mText, text);
15 | }
16 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/TextMatcherFactory.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | public abstract class TextMatcherFactory {
6 | public abstract boolean onMatch(@Nullable String text);
7 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/ToastDuration.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.widget.Toast;
4 |
5 | public enum ToastDuration {
6 | LENGTH_SHORT(Toast.LENGTH_SHORT),
7 | LENGTH_LONG(Toast.LENGTH_LONG);
8 |
9 | private int mValue;
10 |
11 | ToastDuration(int value) {
12 | mValue = value;
13 | }
14 |
15 | public int value() {
16 | return mValue;
17 | }
18 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/UiNode.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.graphics.Rect;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import android.view.accessibility.AccessibilityNodeInfo;
11 |
12 | import java.util.ArrayList;
13 | import java.util.Iterator;
14 | import java.util.List;
15 |
16 | public class UiNode {
17 | private AccessibilityNodeInfo mAccessibilityNodeInfo;
18 | private String mClassName;
19 | private String mText;
20 | private String mDescription;
21 | private final ArrayList mChilds = new ArrayList<>();
22 | private int mFormIndex = -1;
23 |
24 | public static class NullChildAccessibilityNodeInfoException extends MyException {
25 | public NullChildAccessibilityNodeInfoException() {
26 | super();
27 | }
28 |
29 | public NullChildAccessibilityNodeInfoException(String detailMessage) {
30 | super(detailMessage);
31 | }
32 |
33 | public NullChildAccessibilityNodeInfoException(String detailMessage, Throwable throwable) {
34 | super(detailMessage, throwable);
35 | }
36 |
37 | public NullChildAccessibilityNodeInfoException(Throwable throwable) {
38 | super(throwable);
39 | }
40 | }
41 |
42 | public UiNode(@NonNull AccessibilityNodeInfo accessibilityNodeInfo) throws NullChildAccessibilityNodeInfoException, MyException {
43 | if (null == accessibilityNodeInfo) {
44 | throw new NullPointerException("accessibilityNodeInfo");
45 | }
46 | try {
47 | mAccessibilityNodeInfo = accessibilityNodeInfo;
48 | mClassName = StringUtil.toEb(accessibilityNodeInfo.getClassName());
49 | mText = StringUtil.toNb(accessibilityNodeInfo.getText());
50 | mDescription = StringUtil.toNb(accessibilityNodeInfo.getContentDescription());
51 | int count = accessibilityNodeInfo.getChildCount();
52 | for (int i = 0; i < count; i++) {
53 | AccessibilityNodeInfo childAccessibilityNodeInfo = accessibilityNodeInfo.getChild(i);
54 | if (null == childAccessibilityNodeInfo) {
55 | throw new NullChildAccessibilityNodeInfoException("出现空的子AccessibilityNodeInfo");
56 | }
57 | mChilds.add(new UiNode(childAccessibilityNodeInfo));
58 | }
59 | } catch (NullChildAccessibilityNodeInfoException e) {
60 | throw e;
61 | } catch (MyException e) {
62 | throw e;
63 | } catch (Exception e) {
64 | throw new MyException(e);
65 | }
66 | }
67 |
68 | public void recycle() {
69 | for (UiNode childNode : mChilds) {
70 | childNode.recycle();
71 | }
72 | if (null != mAccessibilityNodeInfo) {
73 | mAccessibilityNodeInfo.recycle();
74 | }
75 | }
76 |
77 | @NonNull
78 | public String getClassName() {
79 | return mClassName;
80 | }
81 |
82 | @Nullable
83 | public String getText() {
84 | return mText;
85 | }
86 |
87 | @Nullable
88 | public String getDescription() {
89 | return mDescription;
90 | }
91 |
92 | @NonNull
93 | public Iterator childIterator() {
94 | return mChilds.iterator();
95 | }
96 |
97 | public int getChildCount() {
98 | return mChilds.size();
99 | }
100 |
101 | @Nullable
102 | public UiNode getChild(int index) {
103 | try {
104 | return mChilds.get(index);
105 | } catch (Exception e) {
106 | return null;
107 | }
108 | }
109 |
110 | public void setFormIndex(int index) {
111 | mFormIndex = index;
112 | }
113 |
114 | public int getFormIndex() {
115 | return mFormIndex;
116 | }
117 |
118 | @Nullable
119 | public List findChildsByText(@NonNull String text) {
120 | if (null == text) {
121 | throw new NullPointerException("text");
122 | }
123 | List childs = new ArrayList<>();
124 | findChildsByText(childs, this, text);
125 | return childs;
126 | }
127 |
128 | private static void findChildsByText(@NonNull List childs, @NonNull UiNode uiNode, @NonNull String text) {
129 | if (null == childs) {
130 | throw new NullPointerException("childs");
131 | }
132 | if (null == uiNode) {
133 | throw new NullPointerException("uiNode");
134 | }
135 | if (null == text) {
136 | throw new NullPointerException("text");
137 | }
138 | if (uiNode.getText().equals(text)) {
139 | childs.add(uiNode);
140 | }
141 | for (UiNode child : uiNode.mChilds) {
142 | child.findChildsByText(childs, child, text);
143 | }
144 | }
145 |
146 | @Nullable
147 | public UiNode getNode(@NonNull int[] path) throws MyException {
148 | return getNode(path, 0);
149 | }
150 |
151 | @Nullable
152 | private UiNode getNode(@NonNull int[] path, int level) throws MyException {
153 | if (null == path) {
154 | throw new NullPointerException("path");
155 | }
156 | if (path.length <= level) {
157 | throw new MyException("路径表示异常");
158 | }
159 | if (0 == level) {
160 | if (0 != path[0]) {
161 | throw new MyException("路径表示错误");
162 | }
163 | }
164 | if (path.length == level + 1) {
165 | return this;
166 | }
167 | int index = path[level + 1];
168 | for (UiNode child : mChilds) {
169 | if (child.getFormIndex() == index) {
170 | return child.getNode(path, level + 1);
171 | }
172 | }
173 | return null;
174 | }
175 |
176 | public void click() throws MyException {
177 | try {
178 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
179 | } catch (Exception e) {
180 | throw new MyException(e);
181 | }
182 | }
183 |
184 | public void longClick() throws MyException {
185 | try {
186 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
187 | } catch (Exception e) {
188 | throw new MyException(e);
189 | }
190 | }
191 |
192 | public void setEditText(@NonNull Context context, @NonNull String text) throws MyException {
193 | if (null == context) {
194 | throw new NullPointerException("context");
195 | }
196 | if (null == text) {
197 | throw new NullPointerException("text");
198 | }
199 | try {
200 | //清空内容
201 | Bundle arguments = new Bundle();
202 | arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, AccessibilityNodeInfo
203 | .MOVEMENT_GRANULARITY_LINE);
204 | arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
205 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
206 | arguments);
207 | //粘贴内容
208 | ClipData clipData = ClipData.newPlainText(context.getPackageName(), text);
209 | ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
210 | clipboardManager.setPrimaryClip(clipData);
211 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);
212 | } catch (Exception e) {
213 | throw new MyException(e);
214 | }
215 | }
216 |
217 | public void scrollForward() throws MyException {
218 | try {
219 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
220 | } catch (Exception e) {
221 | throw new MyException(e);
222 | }
223 | }
224 |
225 | public void getBoundsInScreen(Rect bounds) throws MyException {
226 | try {
227 | mAccessibilityNodeInfo.getBoundsInScreen(bounds);
228 | } catch (Exception e) {
229 | throw new MyException(e);
230 | }
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/appanalyst/UiTree.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import android.view.accessibility.AccessibilityNodeInfo;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class UiTree {
11 | public static final List PRESET_FORM = new ArrayList<>();
12 |
13 | private final String mClassName;
14 | private final UiNode mNode;
15 | private final FormTree mFoundForm;
16 | private final String mLog;
17 |
18 | public UiTree(@NonNull String className, @Nullable AccessibilityNodeInfo accessibilityNodeInfo) throws UiNode.NullChildAccessibilityNodeInfoException, MyException {
19 | if (null == className) {
20 | throw new NullPointerException("className");
21 | }
22 | mClassName = className;
23 | mNode = (null == accessibilityNodeInfo ? null : new UiNode(getRootAccessibilityNodeInfo(accessibilityNodeInfo)));
24 | mFoundForm = findForm();
25 | mLog = obtainLog();
26 | }
27 |
28 | public static AccessibilityNodeInfo getRootAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo accessibilityNodeInfo) throws MyException {
29 | if (null == accessibilityNodeInfo) {
30 | throw new NullPointerException("accessibilityNodeInfo");
31 | }
32 | AccessibilityNodeInfo current = null;
33 | try {
34 | current = accessibilityNodeInfo;
35 | while (true) {
36 | AccessibilityNodeInfo parent = current.getParent();
37 | if (null == parent) {
38 | return current;
39 | }
40 | current = parent;
41 | }
42 | } catch (Exception e) {
43 | if (null != current) {
44 | current.recycle();
45 | }
46 | throw new MyException(e);
47 | }
48 | }
49 |
50 | public void recycle() {
51 | if (null != mNode) {
52 | mNode.recycle();
53 | }
54 | }
55 |
56 | @NonNull
57 | public String getClassName() {
58 | return mClassName;
59 | }
60 |
61 | @NonNull
62 | public UiNode getNode() {
63 | return mNode;
64 | }
65 |
66 | @Nullable
67 | public FormTree getFoundForm() {
68 | return mFoundForm;
69 | }
70 |
71 | @NonNull
72 | public String getFoundFormPage() {
73 | return null == mFoundForm ? "" : mFoundForm.getFormPage();
74 | }
75 |
76 | @NonNull
77 | public UiNode getNode(@NonNull String pathName) throws MyException {
78 | if (null == pathName) {
79 | throw new NullPointerException("pathName");
80 | }
81 | if (null == mNode) {
82 | throw new MyException("没有结点");
83 | }
84 | if (null == mFoundForm) {
85 | throw new MyException("没有匹配的Form");
86 | }
87 | int[] path = mFoundForm.getControlPath(pathName);
88 | if (null == path) {
89 | throw new MyException("没有匹配的路径名");
90 | }
91 | UiNode node;
92 | try {
93 | node = mNode.getNode(path);
94 | } catch (MyException e) {
95 | throw new MyException("获取结点异常", e);
96 | }
97 | if (null == node) {
98 | throw new MyException("没有找到结点");
99 | }
100 | return node;
101 | }
102 |
103 | @NonNull
104 | public FormTree findForm() {
105 | for (FormTree formTree : PRESET_FORM) {
106 | if (mClassName.equals(formTree.getClassName())) {
107 | if (null != mNode) {
108 | try {
109 | formTree.getControl().check(mNode);
110 | return formTree;
111 | } catch (CheckFormResult e) {
112 | int path[] = e.getPath();
113 | String text = "检查 # " + formTree.getFormPage() + "\n[";
114 | for (int i = 0; i < path.length; i++) {
115 | if (0 != i) {
116 | text += ",";
117 | }
118 | text += i;
119 | }
120 | text += "]\n[";
121 | for (int i = 0; i < path.length; i++) {
122 | if (0 != i) {
123 | text += ",";
124 | }
125 | text += path[i];
126 | }
127 | text += "]\n: " + e.getMessage();
128 | Log.e(text);
129 | }
130 | }
131 | }
132 | }
133 | return null;
134 | }
135 |
136 | @NonNull
137 | public String getLog() {
138 | return mLog;
139 | }
140 |
141 | @NonNull
142 | public String obtainLog() {
143 | String log = "控件树:ClassName=" + mClassName + "\n";
144 | if (null == mNode) {
145 | return log + "空";
146 | } else {
147 | return log + obtainChildLog(0, 0, mNode);
148 | }
149 | }
150 |
151 | private String obtainChildLog(int level, int index, UiNode uiNode) {
152 | if (null == uiNode) {
153 | return "";
154 | }
155 | String log = "" + level + "[" + index + "]";
156 | for (int i = 0; i <= level; i++) {
157 | log += "---";
158 | }
159 | log += uiNode.getClassName() + "|" + uiNode.getText() + "|" + uiNode.getDescription();
160 | for (int i = 0; i < uiNode.getChildCount(); i++) {
161 | String childLog = obtainChildLog(level + 1, i, uiNode.getChild(i));
162 | if (!"".equals(childLog)) {
163 | log += "\n" + childLog;
164 | }
165 | }
166 | return log;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/qqanalyst/FormPage.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.qqanalyst;
2 |
3 | import silence.com.cn.a310application.appanalyst.DescriptionMatcher;
4 | import silence.com.cn.a310application.appanalyst.FormControl;
5 | import silence.com.cn.a310application.appanalyst.FormTree;
6 | import silence.com.cn.a310application.appanalyst.Macro;
7 | import silence.com.cn.a310application.appanalyst.TextMatcher;
8 | import silence.com.cn.a310application.appanalyst.UiTree;
9 |
10 | public enum FormPage {
11 | QQ_MainActivity_Message, //QQ主页面:消息Fragment
12 | QQ_MainActivity_Dynamic, //QQ主页面:动态Fragment
13 | ;
14 |
15 | static {
16 | try {
17 | readyForm_MainActivity_message();
18 | readyForm_MainActivity_dynamic();
19 |
20 | } catch (Exception e) {
21 | throw new RuntimeException("准备控件结构异常", e);
22 | }
23 | }
24 |
25 | private static void readyForm_MainActivity_dynamic() {
26 | FormControl formControl = new FormControl("android.widget.FrameLayout")//0
27 | .set(0, new FormControl("android.widget.LinearLayout")//1
28 | .set(0, new FormControl("android.widget.FrameLayout")//2
29 | )
30 | );
31 | }
32 |
33 | /**
34 | * 创建主页面的消息布局树
35 | */
36 | private static void readyForm_MainActivity_message() {
37 | FormControl formControl = new FormControl("android.widget.FrameLayout")//0
38 | .set(0,new FormControl("android.widget.LinearLayout")//1
39 | .set(0,new FormControl("android.widget.FrameLayout")//2
40 | .set(0,new FormControl("android.widget.FrameLayout")//3
41 | .set(0,new FormControl("android.widget.TabHost")//4
42 | .set(0,new FormControl("android.widget.FrameLayout")//5
43 | .set(0,new FormControl("android.widget.FrameLayout")//6
44 | .set(0,new FormControl("android.widget.FrameLayout")//7
45 | .set(0,new FormControl("android.widget.RelativeLayout")//8
46 | .set(0,new FormControl("android.widget.RelativeLayout")//9
47 | .set(0,new FormControl("android.view.View")//10
48 |
49 | )
50 | .set(1,new FormControl("android.widget.RelativeLayout")//10
51 | .set(0,new FormControl("android.widget.RadioGroup")//11
52 | .set(0,new FormControl("android.widget.RadioButton", new TextMatcher("消息"))//12
53 |
54 | )
55 | .set(1,new FormControl("android.widget.RadioButton", new TextMatcher("电话"))//12
56 |
57 | )
58 | )
59 | .set(1,new FormControl("android.widget.ImageView", new DescriptionMatcher("快捷入口"))//11
60 |
61 | )
62 | )
63 | )
64 | .set(1,new FormControl("android.widget.RelativeLayout")//9
65 | .set(0,new FormControl("android.widget.AbsListView")//10
66 | .set(0,new FormControl("android.widget.RelativeLayout")//11
67 | .set(0,new FormControl("android.widget.EditText", new DescriptionMatcher("搜索"))//12
68 |
69 | )
70 | .set(1,new FormControl("android.widget.TextView", new TextMatcher("搜索"))//12
71 |
72 | )
73 | )
74 | .set(1,new FormControl("android.widget.LinearLayout")//11
75 | .set(0,new FormControl("android.widget.RelativeLayout")//12
76 | .set(0,new FormControl("android.widget.ImageView")//13
77 |
78 | )
79 | .set(1,new FormControl("android.widget.RelativeLayout")//13
80 | .set(0,new FormControl("android.view.View")//14
81 |
82 | )
83 | .set(1,new FormControl("android.view.View")//14
84 |
85 | )
86 | .set(2,new FormControl("android.widget.TextView")//14
87 |
88 | )
89 |
90 | )
91 |
92 | )
93 | .set(1, new FormControl("android.view.View", new DescriptionMatcher("置顶"))//2
94 | )
95 | .set(2, new FormControl("android.view.View", new DescriptionMatcher("删除"))//12
96 | )
97 | )
98 | .set(2, new FormControl("android.widget.LinearLayout")//11
99 | .set(0, new FormControl("android.widget.RelativeLayout")//12
100 | .set(0, new FormControl("android.widget.ImageView")//13
101 | )
102 | .set(1, new FormControl("android.widget.RelativeLayout")//13
103 | .set(0, new FormControl("android.view.View")//14
104 | )
105 | .set(1, new FormControl("android.view.View")//14
106 | )
107 | )
108 | )
109 | .set(1, new FormControl("android.view.View", new DescriptionMatcher("置顶"))//12
110 | )
111 | .set(2, new FormControl("android.view.View", new DescriptionMatcher("删除"))//12
112 | )
113 | )
114 | .set(3, new FormControl("android.widget.LinearLayout")//11
115 | .set(0, new FormControl("android.widget.RelativeLayout")//12
116 | .set(0, new FormControl("android.widget.ImageView")//13
117 | )
118 | .set(1, new FormControl("android.widget.RelativeLayout")//13
119 | .set(0, new FormControl("android.view.View")//14
120 | )
121 | .set(1, new FormControl("android.view.View")//14
122 | )
123 | )
124 | )
125 | .set(1, new FormControl("android.view.View", new DescriptionMatcher("置顶"))//12
126 | )
127 | .set(2, new FormControl("android.view.View", new DescriptionMatcher("删除"))//12
128 | )
129 | )
130 | )
131 | .set(1, new FormControl("android.widget.FrameLayout")//10
132 | )
133 | .set(2, new FormControl("android.view.View")//10
134 | )
135 | .set(3, new FormControl("android.widget.RelativeLayout")//10
136 | )
137 | )
138 | )
139 | )
140 | .set(1, new FormControl("android.widget.RelativeLayout")//7
141 | .set(0, new FormControl("android.widget.Button", new DescriptionMatcher("帐户及设置"))//8
142 | )
143 | .set(1, new FormControl("android.widget.FrameLayout")//8
144 | .set(0, new FormControl("android.widget.ImageView")//9
145 | )
146 | )
147 | .set(2, new FormControl("android.widget.TextView")//8
148 | )
149 | )
150 | .set(2, new FormControl("android.widget.TabWidget")//7
151 | .set(0, new FormControl("android.widget.RelativeLayout")//8
152 | .set(0, new FormControl("android.widget.ImageView")//9
153 | )
154 | .set(1, new FormControl("android.widget.ImageView")//9
155 | )
156 | .set(2, new FormControl("android.widget.TextView")//9
157 | )
158 | .set(3, new FormControl("android.widget.ImageView")//9
159 | )
160 | )
161 | .set(1, new FormControl("android.widget.FrameLayout")//8
162 | .set(0, new FormControl("android.widget.RelativeLayout")//9
163 | .set(0, new FormControl("android.widget.ImageView")//10
164 | )
165 | .set(1, new FormControl("android.widget.ImageView")//10
166 | )
167 | .set(2, new FormControl("android.widget.ImageView")//10
168 | )
169 | )
170 | )
171 | .set(2, new FormControl("android.widget.FrameLayout")//8
172 | .set(0, new FormControl("android.widget.RelativeLayout")//9
173 | .set(0, new FormControl("android.widget.ImageView")//10消息
174 | )
175 | .set(1, new FormControl("android.widget.ImageView")//10动态
176 | )
177 | .set(2, new FormControl("android.widget.ImageView")//10联系人
178 | )
179 | )
180 | .set(1, new FormControl("android.widget.LinearLayout")//9
181 | .set(0, new FormControl("android.widget.ImageView")//10
182 | )
183 | )
184 | )
185 | )
186 | )
187 | )
188 | )
189 | )
190 | )
191 | );
192 | FormTree formTree = new FormTree(FormPage.QQ_MainActivity_Message.name(), "com.tencent.mobileqq.activity.SplashActivity", formControl);
193 | formTree.addControlPath("动态", new int[]{0, 0, 0, 0, 0, 0, 0, 2, 2});
194 | formTree.addControlPath("电话", new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1});
195 | presetForm(AddWhere.Normal, formTree);
196 |
197 | }
198 |
199 | private enum AddWhere {
200 | Back,
201 | Normal,
202 | }
203 |
204 | private static void presetForm(AddWhere addWhere, FormTree formTree) {
205 | if (Macro.Debug) {
206 | for (FormTree item : UiTree.PRESET_FORM) {
207 | if (item.getFormPage().equals(formTree.getFormPage())) {
208 | throw new ArrayStoreException("代码错误");
209 | }
210 | }
211 | }
212 | switch (addWhere) {
213 | case Normal:
214 | UiTree.PRESET_FORM.add(0, formTree);
215 | break;
216 | case Back:
217 | UiTree.PRESET_FORM.add(formTree);
218 | break;
219 | }
220 | }
221 |
222 | }
223 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/qqanalyst/QQApplication.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.qqanalyst;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 |
6 | import silence.com.cn.a310application.R;
7 | import silence.com.cn.a310application.appanalyst.AnalystApplication;
8 | import silence.com.cn.a310application.appanalyst.AnalystService;
9 | import silence.com.cn.a310application.appanalyst.MyException;
10 |
11 | public class QQApplication extends AnalystApplication {
12 | public static Context mContext;
13 |
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 | mContext = getApplicationContext();
18 | FormPage.values();
19 | try {
20 | AnalystService.init(QQSimulateBusiness.class);
21 | } catch (MyException e) {
22 | e.printStackTrace();
23 | }
24 | }
25 |
26 | public static Context getContext() {
27 | return mContext;
28 | }
29 |
30 | @NonNull
31 | @Override
32 | public String getAppName() {
33 | return getText(R.string.app_name).toString();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/util/BashHelper.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.util;
2 |
3 | import android.content.Context;
4 | import android.content.res.AssetManager;
5 | import android.os.Environment;
6 | import android.text.TextUtils;
7 | import android.util.Log;
8 |
9 | import java.io.BufferedReader;
10 | import java.io.Closeable;
11 | import java.io.DataOutputStream;
12 | import java.io.File;
13 | import java.io.FileOutputStream;
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.InputStreamReader;
17 | import java.io.LineNumberReader;
18 | import java.io.OutputStream;
19 |
20 | /**
21 | * 执行Linux命令的工具类
22 | *
23 | * Created by xuqingfu on 2016/10/28.
24 | */
25 | public class BashHelper {
26 | private static final String TAG = "BashHelper";
27 | private static final String CAPTURE_TOOL = "tcpdump";
28 | /**抓包数据包数据存放文件*/
29 | public static final String DEST_FILE = Environment.getExternalStorageDirectory() + File.separator + "capture.txt";
30 |
31 | /**
32 | * 开始拦截网络数据包
33 | * @param context
34 | * @return 返回是否拦截出现异常,true: 出现异常,拦截中断, false: 拦截正常
35 | */
36 | public static boolean startCapture(Context context) {
37 | InputStream is = null;
38 | OutputStream os = null;
39 | boolean retVal = false;
40 | try {
41 | AssetManager am = context.getAssets();
42 | is = am.open(CAPTURE_TOOL);
43 | File sdcardFile = Environment.getExternalStorageDirectory();
44 | File dstFile = new File(sdcardFile, CAPTURE_TOOL);
45 |
46 | os = new FileOutputStream(dstFile);
47 | copyStream(is, os);
48 |
49 | String[] commands = new String[7];
50 | commands[0] = "adb shell";
51 | //tcpdump -vv -s 0 -w /sdcard/ca.txt -vnn host www.baidu.com
52 | //commands[2] = "tcpdump -p -vv -s 0 -w /sdcard/capture.txt";
53 | commands[2] = "tcpdump -p -v -s 0 -w /sdcard/demo.txt -vnn host jubao.qq.com";
54 | /* commands[1] = "su";
55 | commands[2] = "cp -rf " + dstFile.toString() + " /system/xbin/tcpdump";
56 | commands[3] = "rm -r " + dstFile.toString();
57 | commands[4] = "chmod 777 /system/xbin/tcpdump";
58 | commands[5] = "cd /system/xbin";
59 | commands[6] = "tcpdump -i any -p -s 0 -w " + DEST_FILE;*/
60 | //过滤IP183.3.226.32
61 |
62 | Process proc = execCmd(commands);
63 | /* //使用wairFor()可以等待命令执行完成以后才返回
64 | if (proc.waitFor() != 0) {
65 | System.err.println("exit value = " + proc.exitValue());
66 | }*/
67 |
68 | } catch (Exception e) {
69 | e.printStackTrace();
70 | Log.i(TAG, "抓包出现问题:" + e.getMessage());
71 | retVal = true;
72 | } finally {
73 | closeSafely(is);
74 | closeSafely(os);
75 | }
76 |
77 | return retVal;
78 | }
79 |
80 | /**
81 | * 停止拦截本地网络数据包
82 | * 主要操作:找出所有的带有tcpdump的进程,并结束该进程
83 | * @param context
84 | */
85 | public static void stopCapture(Context context) {
86 | String[] commands = new String[2];
87 | commands[0] = "adb shell";
88 | commands[1] = "ps|grep tcpdump|grep root|awk '{print $2}'";
89 | Process process = execCmd(commands);
90 | String result = parseInputStream(process.getInputStream());
91 | if (!TextUtils.isEmpty(result)) {
92 | String[] pids = result.split("\n");
93 | if (null != pids) {
94 | String[] killCmds = new String[pids.length];
95 | for (int i = 0; i < pids.length; ++i) {
96 | killCmds[i] = "kill -9 " + pids[i];
97 | }
98 | execCmd(killCmds);
99 | Log.d(TAG, "停止抓包");
100 | }
101 | }
102 | }
103 |
104 | public static Process execCmd(String command) {
105 | return execCmd(new String[] { command }, true);
106 | }
107 |
108 | public static Process execCmd(String[] commands) {
109 | return execCmd(commands, true);
110 | }
111 |
112 | public static Process execCmd(String[] commands, boolean waitFor) {
113 | Process suProcess = null;
114 | try {
115 | suProcess = Runtime.getRuntime().exec("su");
116 | DataOutputStream os = new DataOutputStream(suProcess.getOutputStream());
117 | for (String cmd : commands) {
118 | if (!TextUtils.isEmpty(cmd)) {
119 | os.writeBytes(cmd + "\n");
120 | }
121 | }
122 | os.flush();
123 | os.writeBytes("exit\n");
124 | os.flush();
125 | } catch (IOException e) {
126 | e.printStackTrace();
127 | }
128 |
129 | showShell(suProcess);
130 |
131 | if (waitFor) {
132 | boolean retval = false;
133 | try {
134 | int suProcessRetval = suProcess.waitFor();
135 |
136 | if (255 != suProcessRetval) {
137 | retval = true;
138 | } else {
139 | retval = false;
140 | Log.e(TAG, "exit value = " + suProcess.exitValue());
141 | }
142 | } catch (Exception ex) {
143 | ex.printStackTrace();
144 | }
145 | }
146 |
147 | return suProcess;
148 | }
149 |
150 | /**
151 | * 显示Linux命令执行后返回值
152 | * @param suProcess
153 | */
154 | private static void showShell(Process suProcess) {
155 | try {
156 | InputStream inputstream = suProcess.getInputStream();
157 | InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
158 | BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
159 | // read the ls output
160 | String line = "";
161 | StringBuilder sb = new StringBuilder(line);
162 | while ((line = bufferedreader.readLine()) != null) {
163 | //System.out.println(line);
164 | sb.append(line);
165 | sb.append('\n');
166 | }
167 | Log.d(TAG, "shell:" + sb.toString());
168 | } catch (IOException e) {
169 | e.printStackTrace();
170 | }
171 | }
172 |
173 | /**
174 | * 文件拷贝
175 | * @param is
176 | * @param os
177 | */
178 | private static void copyStream(InputStream is, OutputStream os) {
179 | final int BUFFER_SIZE = 1024;
180 | try {
181 | byte[] bytes = new byte[BUFFER_SIZE];
182 | for (;;) {
183 | int count = is.read(bytes, 0, BUFFER_SIZE);
184 | if (count == -1) {
185 | break;
186 | }
187 |
188 | os.write(bytes, 0, count);
189 | }
190 | } catch (IOException e) {
191 | e.printStackTrace();
192 | }
193 | }
194 |
195 | /**
196 | * 释放IO流资源
197 | * @param is
198 | */
199 | private static void closeSafely(Closeable is) {
200 | try {
201 | if (null != is) {
202 | is.close();
203 | }
204 | } catch (IOException e) {
205 | e.printStackTrace();
206 | }
207 | }
208 |
209 | /**
210 | * 读取IO输入流中的数据
211 | * @param is
212 | * @return 流数据字符串
213 | */
214 | private static String parseInputStream(InputStream is) {
215 | InputStreamReader isr = new InputStreamReader(is);
216 | BufferedReader br = new BufferedReader(isr);
217 | String line;
218 | StringBuilder sb = new StringBuilder();
219 | try {
220 | while ( (line = br.readLine()) != null) {
221 | sb.append(line).append("\n");
222 | }
223 | } catch (IOException e) {
224 | e.printStackTrace();
225 | }
226 |
227 | return sb.toString();
228 | }
229 | }
230 |
231 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/util/FilterFile.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.util;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 | import java.io.InputStreamReader;
11 | import java.util.regex.Matcher;
12 | import java.util.regex.Pattern;
13 |
14 | /**
15 | * 从数据包文件中提取QQ号
16 | *
17 | * Created by littleRich on 2016/10/28.
18 | */
19 |
20 | public class FilterFile {
21 | public static String extractQQ(String f){
22 | long begin = System.currentTimeMillis();
23 | File file = new File("/sdcard/demo.txt");
24 | try {
25 | InputStream inputstream = new FileInputStream(file);
26 | InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
27 | BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
28 | String line;
29 | // StringBuilder sb = new StringBuilder(line);
30 | while ((line = bufferedreader.readLine()) != null) {
31 | if(line.contains("eviluin")){
32 | System.out.println(line);
33 | Log.d("BashHelper", line);
34 | String regex = "eviluin=(.*)&appname";
35 | Pattern pattern = Pattern.compile(regex);
36 | Matcher matcher = pattern.matcher(line);
37 | while(matcher.find()){
38 | Log.d("BashHelper", "获取到附近的人QQ号:" + matcher.group(1));
39 | }
40 | break;
41 | }
42 | }
43 | long end = System.currentTimeMillis() - begin;
44 | Log.d("BashHelper", "解析数据包用时(毫秒):" + end);
45 | return line;
46 | } catch (IOException e) {
47 | e.printStackTrace();
48 | }
49 | return null;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/util/SilenceLog.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.util;
2 |
3 | import android.os.Environment;
4 | import android.util.Log;
5 | import android.widget.Toast;
6 |
7 | import java.io.File;
8 | import java.io.FileOutputStream;
9 | import java.nio.ByteBuffer;
10 | import java.text.SimpleDateFormat;
11 | import java.util.Date;
12 | import java.util.Locale;
13 |
14 |
15 | public class SilenceLog {
16 |
17 |
18 | private static final String TAG = "Silence";
19 | private static Toast toast;
20 |
21 | public static void d(String msg) {
22 | // if (Util.isDebug()) {
23 | Log.d(TAG, msg);
24 | f(msg);
25 | // }
26 | }
27 |
28 | public static void v(String msg) {
29 | // if (Util.isDebug()) {
30 | Log.v(TAG, msg);
31 | f(msg);
32 | // }
33 | }
34 |
35 | public static void e(String msg) {
36 | // if (Util.isDebug()) {
37 | Log.e(TAG, msg);
38 | f(msg);
39 | // }
40 | }
41 |
42 | public static void i(String msg) {
43 | // if (Util.isDebug()) {
44 | Log.i(TAG, msg);
45 | f(msg);
46 | // }
47 | }
48 |
49 | public static void w(String msg) {
50 | // if (Util.isDebug()) {
51 | Log.w(TAG, msg);
52 | f(msg);
53 | // }
54 | }
55 |
56 | public static void f(String msg) {
57 | // if (Util.isDebug()) {
58 | // writeFile(msg);
59 | // }
60 | }
61 |
62 | private static void writeFile(String msg) {
63 | String sdStatus = Environment.getExternalStorageState();
64 | if (!sdStatus.equals(Environment.MEDIA_MOUNTED)) {
65 | Log.d("TestFile", "SD card is not avaiable/writeable right now.");
66 | return;
67 | }
68 | try {
69 | String pathName = Environment.getExternalStorageDirectory().getPath() + "/ForeSightLog/";
70 | String fileName = "runningLog.txt";
71 | File path = new File(pathName);
72 | File file = new File(pathName + fileName);
73 | if (!path.exists()) {
74 | Log.d("runningLog", "Create the path:" + pathName);
75 | path.mkdir();
76 | }
77 | if (!file.exists()) {
78 | Log.d("runningLog", "Create the file:" + fileName);
79 | file.createNewFile();
80 | }
81 | FileOutputStream stream = new FileOutputStream(file, true);
82 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINESE);
83 | String s = "[" + dateFormat.format(new Date()) + "][thread:" + Thread.currentThread().getId() + "][" +
84 | msg + "]\r\n";
85 | byte[] buf = s.getBytes();
86 | stream.write(buf);
87 | stream.close();
88 |
89 | } catch (Exception e) {
90 | Log.e("TestFile", "Error on writeFilToSD.");
91 | e.printStackTrace();
92 | }
93 | }
94 |
95 |
96 | /**
97 | * @description 显示当前行的相关信息,如:当前文件、当前函数、当前行号
98 | */
99 | public static void l() {
100 | d(getCurrentLineInfo());
101 | }
102 |
103 | /**
104 | * @return 当前行的相关信息
105 | * @description 获取当前行的相关信息
106 | */
107 | public static String getCurrentLineInfo() {
108 | StackTraceElement ste = new Throwable().getStackTrace()[1];
109 | return "FileName:[" + ste.getFileName() + "] MethodName: [" + ste.getMethodName() + "]: Line [" + ste
110 | .getLineNumber() + "]";
111 | }
112 |
113 | /**
114 | * @return
115 | * @description 将ByteBuff转换成16进制
116 | */
117 | public static String printHex(String s, ByteBuffer buff) {
118 | SilenceLog.w(s + ": " + byteToHex(buff.array()));
119 | return byteToHex(buff.array());
120 | }
121 |
122 | public static String byteToHex(byte[] buff) {
123 | if (null != buff) {
124 | String hexString = "";
125 | for (int i = 0; i < buff.length; i++) {
126 | String hex = Integer.toHexString(buff[i] & 0xFF);
127 | if (hex.length() == 1) {
128 | hexString += '0' + hex;
129 | } else {
130 | hexString += hex;
131 | }
132 | }
133 | String regex = "(.{8})";
134 | hexString = hexString.replaceAll(regex, "$1 ");
135 | return hexString.toUpperCase();
136 | }
137 | return "";
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/Demo/src/main/java/silence/com/cn/a310application/util/Util.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.util;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.ActivityManager;
5 | import android.app.ActivityManager.RunningAppProcessInfo;
6 | import android.content.ComponentName;
7 | import android.content.Context;
8 | import android.content.pm.ApplicationInfo;
9 | import android.content.pm.PackageInfo;
10 | import android.content.pm.PackageManager;
11 | import android.net.ConnectivityManager;
12 | import android.net.NetworkInfo;
13 | import android.os.Environment;
14 | import android.text.TextUtils;
15 | import android.view.View;
16 | import android.view.ViewGroup;
17 | import android.widget.ListAdapter;
18 | import android.widget.ListView;
19 | import android.widget.Toast;
20 |
21 | import org.json.JSONArray;
22 | import org.json.JSONException;
23 | import org.json.JSONStringer;
24 |
25 | import java.util.List;
26 | import java.util.regex.Matcher;
27 | import java.util.regex.Pattern;
28 |
29 |
30 | public class Util {
31 | private static Context mContext;
32 | private static Toast mToast = null;
33 |
34 | // 限制按钮快速点击
35 | private static long lastClickTime = 0;
36 | private static long DIFF = 500;
37 | private static int lastButtonId = -1;
38 |
39 | public static Toast showToast(Context context, String hint) {
40 |
41 | if (mContext == context) {
42 | mToast.setText(hint);
43 | } else {
44 | mContext = context;
45 | mToast = Toast.makeText(context, hint, Toast.LENGTH_SHORT);
46 | }
47 | mToast.show();
48 | return mToast;
49 | }
50 |
51 | public static Toast showToast(Context context, int i) {
52 |
53 | if (mContext == context) {
54 | mToast.setText(i);
55 | } else {
56 | mContext = context;
57 | mToast = Toast.makeText(context, i, Toast.LENGTH_SHORT);
58 | }
59 | mToast.show();
60 | return mToast;
61 | }
62 |
63 | /**
64 | * @return true: debug false:release
65 | * @description 判断当前程序是否出于DEBUG模式
66 | */
67 | public static boolean isDebug(Context context) {
68 | // 如果是开发使用则直接开启日志,否则根据当前状态判断
69 | try {
70 | ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
71 | return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == ApplicationInfo.FLAG_DEBUGGABLE;
72 | } catch (PackageManager.NameNotFoundException e) {
73 | }
74 | return false;
75 | }
76 |
77 | /**
78 | * @return null may be returned if the specified process not found
79 | */
80 | public static String getProcessName(Context cxt, int pid) {
81 | ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
82 | List runningApps = am.getRunningAppProcesses();
83 | if (runningApps == null) {
84 | return null;
85 | }
86 | for (RunningAppProcessInfo procInfo : runningApps) {
87 | if (procInfo.pid == pid) {
88 | return procInfo.processName;
89 | }
90 | }
91 | return null;
92 | }
93 |
94 | /**
95 | * @return true: 存在可用的网络 flase: 无可用的网络
96 | * @description 判断当前是否有可用的网络
97 | */
98 | public static boolean isNetWorkUseful(Context context) {
99 | boolean result = false;
100 | try {
101 | ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context
102 | .CONNECTIVITY_SERVICE);
103 | if (connectivity != null) {
104 | NetworkInfo info = connectivity.getActiveNetworkInfo();
105 | if (info != null && info.isConnected()) {
106 | if (info.getState() == NetworkInfo.State.CONNECTED) {
107 | result = true;
108 | }
109 | }
110 | }
111 | } catch (Exception e) {
112 | e.printStackTrace();
113 | }
114 | return result;
115 | }
116 |
117 | /**
118 | * @return null :不存在可用的网络 !null:存在可用的网络,并将可用网络返给用户使用
119 | * @description 获取当前可用的网络
120 | */
121 | public static NetworkInfo getActiveNetworkInfo(Context context) {
122 |
123 | ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context
124 | .CONNECTIVITY_SERVICE);
125 |
126 | return connectivityManager.getActiveNetworkInfo();
127 | }
128 |
129 | /***
130 | * @return true:存在 false:不存在
131 | * @description 判断是否存在SDCard
132 | */
133 | public static boolean isSdCardExist() {
134 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
135 | }
136 |
137 | /**
138 | * @return true:最上层 false:!最上层
139 | * @description 判断APP是否运行于最上层
140 | */
141 | public static boolean isRunningForeground(Context context) {
142 | ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
143 | ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
144 | String currentPackageName = cn.getPackageName();
145 | if (!TextUtils.isEmpty(currentPackageName) && currentPackageName.equals(context.getPackageName())) {
146 | return true;
147 | }
148 | return false;
149 | }
150 |
151 | public static String getAppVersionName(Context context) {
152 | String versionName = "";
153 | try {
154 | PackageManager pm = context.getPackageManager();
155 | PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
156 | versionName = pi.versionName;
157 | if (versionName == null || versionName.length() <= 0) {
158 | return "";
159 | }
160 | } catch (Exception e) {
161 | }
162 | return versionName;
163 | }
164 |
165 | public static byte[] getAppVersionCode(Context context) {
166 | byte[] versionCode = new byte[4];
167 | String versionName = "";
168 | try {
169 | PackageManager pm = context.getPackageManager();
170 | PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
171 | versionName = pi.versionName;
172 | String a[] = versionName.split("\\.");
173 | versionCode[0] = (byte) Integer.parseInt(a[0]);
174 | versionCode[1] = (byte) Integer.parseInt(a[1]);
175 | versionCode[2] = (byte) Integer.parseInt(a[2]);
176 | versionCode[3] = (byte) Integer.parseInt(a[3]);
177 | } catch (Exception e) {
178 | }
179 | return versionCode;
180 | }
181 |
182 | /**
183 | * java字节码转字符串
184 | *
185 | * @param b
186 | * @return
187 | */
188 | @SuppressLint("DefaultLocale")
189 | public static String byte2hex(byte[] b) { // 一个字节的数,
190 |
191 | // 转成16进制字符串
192 | String hs = "";
193 | String tmp = "";
194 | for (int n = 0; n < b.length; n++) {
195 | // 整数转成十六进制表示
196 | tmp = (Integer.toHexString(b[n] & 0XFF));
197 | if (tmp.length() == 1) {
198 | hs = hs + "0" + tmp;
199 | } else {
200 | hs = hs + tmp;
201 | }
202 | }
203 | tmp = null;
204 | return hs.toUpperCase(); // 转成大写
205 | }
206 |
207 | /**
208 | * 字符串转java字节码
209 | *
210 | * @param b
211 | * @return
212 | */
213 | public static byte[] hex2byte(byte[] b) {
214 | if ((b.length % 2) != 0) {
215 | throw new IllegalArgumentException("长度不是偶数");
216 | }
217 | byte[] b2 = new byte[b.length / 2];
218 | for (int n = 0; n < b.length; n += 2) {
219 | String item = new String(b, n, 2);
220 | // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个进制字节
221 |
222 | b2[n / 2] = (byte) Integer.parseInt(item, 16);
223 | }
224 | b = null;
225 | return b2;
226 | }
227 |
228 | /**
229 | * dp转成px
230 | *
231 | * @param dipValue
232 | * @return
233 | */
234 | // public static int dip2px(float dipValue) {
235 | // return (int) (dipValue * scale + 0.5f);
236 | // }
237 |
238 | /**
239 | * px转成dp
240 | *
241 | * @param pxValue
242 | * @return
243 | */
244 | // public static int px2dip(float pxValue) {
245 | // return (int) (pxValue / scale + 0.5f);
246 | // }
247 |
248 | /**
249 | * 获取图片的ID
250 | */
251 | public static int getDrawableResId(Context context, String name) {
252 | ApplicationInfo appInfo = context.getApplicationInfo();
253 | int resID = context.getResources().getIdentifier(name, "drawable", appInfo.packageName);
254 | return resID;
255 | }
256 |
257 | /**
258 | * 获取布局的ID
259 | */
260 | public static int getLayoutResId(Context context, String name) {
261 | ApplicationInfo appInfo = context.getApplicationInfo();
262 | int resID = context.getResources().getIdentifier(name, "layout", appInfo.packageName);
263 | return resID;
264 | }
265 |
266 | /**
267 | * 得到有效的字符串数据
268 | */
269 | public static String getValidData(byte[] invalid) {
270 |
271 | int index = 0;
272 | while (index < invalid.length) {
273 | if (0 == invalid[index]) {
274 | break;
275 | }
276 | index++;
277 | }
278 |
279 | byte[] valid = new byte[index];
280 | System.arraycopy(invalid, 0, valid, 0, index);
281 | return new String(valid);
282 |
283 | }
284 |
285 | /**
286 | * 判断两次点击的间隔,如果小于1000,则认为是多次无效点击
287 | *
288 | * @return
289 | */
290 | public static boolean isFastDoubleClick() {
291 | return isFastDoubleClick(-1, DIFF);
292 | }
293 |
294 | /**
295 | * 判断两次点击的间隔,如果小于1000,则认为是多次无效点击
296 | *
297 | * @return
298 | */
299 | public static boolean isFastDoubleClick(int buttonId) {
300 | return isFastDoubleClick(buttonId, DIFF);
301 | }
302 |
303 | /**
304 | * 判断两次点击的间隔,如果小于diff,则认为是多次无效点击
305 | *
306 | * @param diff
307 | * @return
308 | */
309 | public static boolean isFastDoubleClick(int buttonId, long diff) {
310 | long time = System.currentTimeMillis();
311 | long timeD = time - lastClickTime;
312 | if (lastButtonId == buttonId && lastClickTime > 0 && timeD < diff) {
313 | return true;
314 | }
315 |
316 | lastClickTime = time;
317 | lastButtonId = buttonId;
318 | return false;
319 | }
320 |
321 | /**
322 | * @param key
323 | * @param value
324 | * @return
325 | * @throws
326 | * @Title: createJsonString
327 | * @Description: 生成json字符串
328 | * @return: String
329 | */
330 | public static String createJsonString(String key, JSONArray value) {
331 | JSONStringer jsonStringer = new JSONStringer();
332 | try {
333 | jsonStringer.object().key(key).value(value).endObject();
334 | } catch (JSONException e) {
335 | e.printStackTrace();
336 | }
337 | return jsonStringer.toString();
338 | }
339 |
340 | /**
341 | * ip地址校验
342 | *
343 | * @param ipAddress ip地址
344 | * @return boolean
345 | */
346 | public static boolean isIpv4(String ipAddress) {
347 |
348 | String ip = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\." + "" +
349 | "(00?\\d|1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." + "" +
350 | "(00?\\d|1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." + "" +
351 | "(00?\\d|1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
352 |
353 | Pattern pattern = Pattern.compile(ip);
354 | Matcher matcher = pattern.matcher(ipAddress);
355 | return matcher.matches();
356 | }
357 |
358 | /**
359 | * 子网掩码校验
360 | *
361 | * @param mask 子网掩码
362 | * @return boolean
363 | */
364 | public static boolean isMask(String mask) {
365 | Pattern pattern = Pattern.compile("^((128|192)|2(24|4[08]|5[245]))(\\.(0|(128|192)|2((24)|(4[08])|(5[245]))))" +
366 | "" + "{3}$");
367 | Matcher matcher = pattern.matcher(mask);
368 | return matcher.matches();
369 | }
370 |
371 | public static void setListViewHeightBasedOnChildren(ListView listView) {
372 | ListAdapter listAdapter = listView.getAdapter();
373 | if (listAdapter == null) {
374 | // pre-condition
375 | return;
376 | }
377 |
378 | int totalHeight = 0;
379 | for (int i = 0; i < listAdapter.getCount(); i++) {
380 | View listItem = listAdapter.getView(i, null, listView);
381 | listItem.measure(0, 0);
382 | totalHeight += listItem.getMeasuredHeight();
383 | }
384 |
385 | ViewGroup.LayoutParams params = listView.getLayoutParams();
386 | params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
387 | listView.setLayoutParams(params);
388 | }
389 | }
390 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
28 |
29 |
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littleRich/AutoInteraction-Library/b27c7e26e4a65c1c7edbea8524e0be845ef68660/Demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littleRich/AutoInteraction-Library/b27c7e26e4a65c1c7edbea8524e0be845ef68660/Demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littleRich/AutoInteraction-Library/b27c7e26e4a65c1c7edbea8524e0be845ef68660/Demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littleRich/AutoInteraction-Library/b27c7e26e4a65c1c7edbea8524e0be845ef68660/Demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littleRich/AutoInteraction-Library/b27c7e26e4a65c1c7edbea8524e0be845ef68660/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 310Application
3 | QQ舆情分析服务
4 |
5 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Demo/src/main/res/xml/wxa_service.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Demo/src/test/java/silence/com/cn/a310application/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AutoInteraction-Library
2 | 如何集成使用本自动化模拟操作库,可以看我之前写的一个Demo项目,由于工作太忙问题,如果感兴趣的同学比较多,下次再补充使用文档,也可以直接给我发邮件。
3 |
4 | ----------
5 | 注:本开源库难免有些BUG,如有好的提议,麻烦及时反馈,共同技术交流,欢迎Star!
6 |
7 | 关于Android仿真人机交互开源库源码说明,可以看我的一篇博客:[Android自动化模拟操作开源库源码解析](http://littlerich.top/2016/12/05/Android%E8%87%AA%E5%8A%A8%E5%8C%96%E6%A8%A1%E6%8B%9F%E6%93%8D%E4%BD%9C%E5%BC%80%E6%BA%90%E5%BA%93/ "Android自动化模拟操作开源库源码解析")
8 |
--------------------------------------------------------------------------------
/libs/appanalyst/AnalystApplication.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.app.Application;
4 | import android.support.annotation.NonNull;
5 |
6 | public abstract class AnalystApplication extends Application {
7 | @Override
8 | public void onCreate() {
9 | super.onCreate();
10 | Log.init(getAppName());
11 | Log.open();
12 | }
13 |
14 | @Override
15 | public void onTerminate() {
16 | super.onTerminate();
17 | Log.close();
18 | }
19 |
20 | @NonNull
21 | public abstract String getAppName();
22 | }
23 |
--------------------------------------------------------------------------------
/libs/appanalyst/AnalystBusiness.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | public abstract class AnalystBusiness {
6 | private AnalystServiceHandler mServiceHandler;
7 |
8 | protected AnalystBusiness(@NonNull AnalystServiceHandler serviceHandler) {
9 | if (null == serviceHandler) {
10 | throw new NullPointerException("serviceHandler");
11 | }
12 | mServiceHandler = serviceHandler;
13 | }
14 |
15 | protected AnalystServiceHandler getServiceHandler() {
16 | return mServiceHandler;
17 | }
18 |
19 | public abstract void handleUiEvent(@NonNull UiTree uiTree, boolean keepingAlive) throws MyException;
20 | }
21 |
--------------------------------------------------------------------------------
/libs/appanalyst/AnalystService.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.accessibilityservice.AccessibilityService;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Handler;
7 | import android.os.Message;
8 | import android.support.annotation.NonNull;
9 | import android.view.accessibility.AccessibilityEvent;
10 |
11 | import java.lang.reflect.Constructor;
12 |
13 | public class AnalystService extends AccessibilityService {
14 | public static final int ID_POST_EVENT = 1;
15 | public static final int ID_POST_KEEP_ALIVE = 2;
16 |
17 | public static final int DELAY_POST_EVENT = 1000;
18 | public static final int DELAY_KEEP_ALIVE = 15* 1000;
19 |
20 | private static Constructor extends AnalystBusiness> mBusinessClazzConstructor;
21 | private static MyProcess mSuProcess;
22 |
23 | private final Handler mMainHandler = new Handler(new Handler.Callback() {
24 | @Override
25 | public boolean handleMessage(Message msg) {
26 | ((Runnable) msg.obj).run();
27 | return true;
28 | }
29 | });
30 | private final GpsMocker mGpsMocker = new GpsMocker();
31 |
32 | private AnalystBusiness mBusiness;
33 | private String mLastEventClassName;
34 | private int mLastPostingCount;
35 | private int mNullChildAccessibilityNodeInfoExceptionCount;
36 | private int mUnknownUiCount;
37 | private int mKeepAliveCount;
38 |
39 | private boolean mRunning = false;
40 |
41 | public class ServiceHandler implements AnalystServiceHandler {
42 | @Override
43 | public Context getContext() {
44 | return AnalystService.this;
45 | }
46 |
47 | @Override
48 | public GpsMocker getGpsMocker() {
49 | return mGpsMocker;
50 | }
51 |
52 | @Override
53 | public void requirePost() {
54 | if (!mRunning) {
55 | return;
56 | }
57 | postEvent();
58 | }
59 |
60 | @Override
61 | public void requireBack() {
62 | if (!mRunning) {
63 | return;
64 | }
65 | performGlobalAction(GLOBAL_ACTION_BACK);
66 | }
67 |
68 | @Override
69 | public void requireMockClick(PointXY point) {
70 | if (!mRunning) {
71 | return;
72 | }
73 | if (null == point) {
74 | return;
75 | }
76 | try {
77 | mSuProcess.exec("input tap " + point.mX + " " + point.mY);
78 | } catch (MyException e) {
79 | Log.e(new MyException("执行requireMockClick异常", e), AnalystService.this, ToastDuration.LENGTH_SHORT);
80 | }
81 | }
82 |
83 | @Override
84 | public void requireMockLongClick(PointXY point) {
85 | if (!mRunning) {
86 | return;
87 | }
88 | if (null == point) {
89 | return;
90 | }
91 | try {
92 | mSuProcess.exec("input swipe " + point.mX + " " + point.mY + " " + point.mX + " " + point.mY + " 1200");
93 | } catch (MyException e) {
94 | Log.e(new MyException("执行requireMockLongClick异常", e), AnalystService.this, ToastDuration.LENGTH_SHORT);
95 | }
96 | }
97 |
98 | @Override
99 | public void requireMockMove(PointXY2XY pointXY2XY) {
100 | if (!mRunning) {
101 | return;
102 | }
103 | if (null == pointXY2XY) {
104 | return;
105 | }
106 | try {
107 | mSuProcess.exec("input swipe " + pointXY2XY.mFrom.mX + " " + pointXY2XY.mFrom.mY + " " + pointXY2XY
108 | .mTo.mX + " " + pointXY2XY.mTo.mY + " 800");
109 | } catch (MyException e) {
110 | Log.e(new MyException("执行requireMockMove异常", e), AnalystService.this, ToastDuration.LENGTH_SHORT);
111 | }
112 | }
113 | }
114 |
115 | public static void init(@NonNull Class extends AnalystBusiness> businessClazz) throws MyException {
116 | try {
117 | mSuProcess = new MyProcess("su");
118 | } catch (Exception e) {
119 | throw new MyException("获取ROOT权限失败", e);
120 | }
121 | try {
122 | mBusinessClazzConstructor = businessClazz.getConstructor(AnalystServiceHandler.class);
123 | } catch (Exception e) {
124 | throw new MyException(e);
125 | }
126 | }
127 |
128 | @Override
129 | public void onCreate() {
130 | mGpsMocker.init(this);
131 | //SilenceLog.e("onCreate");
132 | }
133 |
134 | @Override
135 | protected void onServiceConnected() {
136 | //SilenceLog.e("onServiceConnected");
137 | Log.i("AnalystService.onServiceConnected");
138 |
139 | try {
140 | mBusiness = mBusinessClazzConstructor.newInstance(new ServiceHandler());
141 | } catch (Exception e) {
142 | throw new RuntimeException(e);
143 | }
144 | mLastEventClassName = null;
145 | mLastPostingCount = 0;
146 | mNullChildAccessibilityNodeInfoExceptionCount = 0;
147 | mUnknownUiCount = 0;
148 | mKeepAliveCount = 0;
149 | mRunning = true;
150 | // WatcherUtil.post2watcher(QQApplication.getContext(), "start");
151 | // WatcherUtil.startAlarm(QQApplication.getContext(), true);
152 | }
153 |
154 |
155 | @Override
156 | public void onInterrupt() {
157 | Log.i("AnalystService.onInterrupt");
158 | mRunning = false;
159 | mMainHandler.removeMessages(ID_POST_EVENT);
160 | mMainHandler.removeMessages(ID_POST_KEEP_ALIVE);
161 | mBusiness = null;
162 | // post2watcher("end");
163 | // SilenceLog.e("中断了==============");
164 | }
165 |
166 | @Override
167 | public boolean onUnbind(Intent intent) {
168 | // SilenceLog.e("onUnbind");
169 | // WatcherUtil.post2watcher(QQApplication.getContext(), "end");
170 | // WatcherUtil.startAlarm(QQApplication.getContext(), false);
171 | return super.onUnbind(intent);
172 | }
173 |
174 |
175 | @Override
176 | public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
177 | if (null == accessibilityEvent) {
178 | return;
179 | }
180 | switch (accessibilityEvent.getEventType()) {
181 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
182 | Log.i("AnalystService.onAccessibilityEvent # AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED");
183 | mLastEventClassName = StringUtil.toNb(accessibilityEvent.getClassName());
184 | postEvent();
185 | }
186 | break;
187 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
188 | Log.i("AnalystService.onAccessibilityEvent # AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED");
189 | if (null != mLastEventClassName) {
190 | postEvent();
191 | }
192 | }
193 | break;
194 | }
195 | }
196 |
197 | private void postEvent() {
198 | postEvent(false);
199 | }
200 |
201 |
202 | private void postEvent(final boolean keepingAlive) {
203 | if (!mRunning) {
204 | return;
205 | }
206 | if (keepingAlive) {
207 | mKeepAliveCount++;
208 | if (mKeepAliveCount > 3) {
209 | // WatcherUtil.post2watcher(QQApplication.getContext(), "KillWeChat");
210 | return;
211 | }
212 | } else {
213 | mKeepAliveCount = 0;
214 | }
215 | // WatcherUtil.post2watcher(QQApplication.getContext(), "postEvent");
216 | mMainHandler.removeMessages(ID_POST_EVENT);
217 | Message message = mMainHandler.obtainMessage(ID_POST_EVENT, new Runnable() {
218 | @Override
219 | public void run() {
220 | mLastPostingCount = 0;
221 | UiTree uiTree = null;
222 | try {
223 | uiTree = new UiTree(mLastEventClassName, getRootInActiveWindow());
224 | Log.i(uiTree.getLog());
225 | if (null == uiTree.getFoundForm()) {
226 | Log.i("UI类型: 未知");
227 | mUnknownUiCount++;
228 | if (mUnknownUiCount > 15) {
229 | mUnknownUiCount = 0;
230 | // WatcherUtil.post2watcher(QQApplication.getContext(), "KillWeChat");
231 | Log.e("未知页面次数太多,返回", AnalystService.this, ToastDuration.LENGTH_SHORT);
232 | performGlobalAction(GLOBAL_ACTION_BACK);
233 | }
234 | } else {
235 | // WatcherUtil.post2watcher(QQApplication.getContext(), "PageChange");
236 | String formPage = uiTree.getFoundFormPage();
237 | Log.i("UI类型: " + formPage);
238 | mUnknownUiCount = 0;
239 | mBusiness.handleUiEvent(uiTree, keepingAlive);
240 | }
241 | mNullChildAccessibilityNodeInfoExceptionCount = 0;
242 | } catch (UiNode.NullChildAccessibilityNodeInfoException e) {
243 | Log.e(new MyException("AnalystService.postEvent # run", e));
244 | mNullChildAccessibilityNodeInfoExceptionCount++;
245 | if (mNullChildAccessibilityNodeInfoExceptionCount > 5) {
246 | Log.e(e, AnalystService.this, ToastDuration.LENGTH_SHORT);
247 | // WatcherUtil.post2watcher(QQApplication.getContext(), "AccessibilityNodeInfo");
248 | } else {
249 | postEvent();
250 | }
251 | } catch (Exception e) {
252 | Log.e(new MyException("AnalystService.postEvent # run", e));
253 | mNullChildAccessibilityNodeInfoExceptionCount = 0;
254 | } finally {
255 | if (null != uiTree) {
256 | uiTree.recycle();
257 | }
258 | }
259 | }
260 | });
261 | mLastPostingCount++;
262 | if (keepingAlive || mLastPostingCount > 20) {
263 | // KeepingAlive的时候,立马执行
264 | // 连续post次数过多,则不再延迟,立马执行
265 | mMainHandler.sendMessage(message);
266 | } else {
267 | mMainHandler.sendMessageDelayed(message, DELAY_POST_EVENT);
268 | }
269 | postKeepAlive();
270 | }
271 |
272 | private void postKeepAlive() {
273 | if (!mRunning) {
274 | return;
275 | }
276 | mMainHandler.removeMessages(ID_POST_KEEP_ALIVE);
277 | Message message = mMainHandler.obtainMessage(ID_POST_KEEP_ALIVE, new Runnable() {
278 | @Override
279 | public void run() {
280 | postEvent(true);
281 | }
282 | });
283 | mMainHandler.sendMessageDelayed(message, DELAY_KEEP_ALIVE);
284 | }
285 |
286 |
287 | }
288 |
--------------------------------------------------------------------------------
/libs/appanalyst/AnalystServiceHandler.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.content.Context;
4 |
5 | public interface AnalystServiceHandler {
6 | Context getContext();
7 |
8 | GpsMocker getGpsMocker();
9 |
10 | void requirePost();
11 |
12 | void requireBack();
13 |
14 | void requireMockClick(PointXY point);
15 |
16 | void requireMockLongClick(PointXY point);
17 |
18 | void requireMockMove(PointXY2XY pointXY2XY);
19 | }
20 |
--------------------------------------------------------------------------------
/libs/appanalyst/CheckFormResult.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class CheckFormResult extends Exception {
4 | private int[] mPath;
5 |
6 | public CheckFormResult(int[] path, String message) {
7 | super(message);
8 | mPath = path;
9 | }
10 |
11 | public int[] getPath() {
12 | return mPath;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/libs/appanalyst/DescriptionMatcher.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | public class DescriptionMatcher extends DescriptionMatcherFactory {
6 | public final String mDescription;
7 |
8 | public DescriptionMatcher(@Nullable String description) {
9 | if (null == description) {
10 | throw new NullPointerException("description");
11 | }
12 | mDescription = description;
13 | }
14 |
15 | @Override
16 | public boolean onMatch(@Nullable String description) {
17 | return StringUtil.compareNb(mDescription, description);
18 | }
19 | }
--------------------------------------------------------------------------------
/libs/appanalyst/DescriptionMatcherFactory.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | public abstract class DescriptionMatcherFactory {
6 | public abstract boolean onMatch(@Nullable String description);
7 | }
--------------------------------------------------------------------------------
/libs/appanalyst/FormControl.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | import java.util.ArrayList;
7 | import java.util.HashSet;
8 | import java.util.Set;
9 |
10 | public class FormControl {
11 | private final String mClassName;
12 | private final TextMatcherFactory mTextMatcher;
13 | private final DescriptionMatcherFactory mDescriptionMatcher;
14 | private final ArrayList mChilds = new ArrayList<>();
15 | private boolean mEssential = true;
16 | private final Set mIncludeTexts = new HashSet<>();
17 | private final Set mExcludeTexts = new HashSet<>();
18 |
19 | public FormControl(@Nullable String className) {
20 | this(className, null, null);
21 | }
22 |
23 | public FormControl(@Nullable String className, @Nullable TextMatcherFactory textMatcher) {
24 | this(className, textMatcher, null);
25 | }
26 |
27 | public FormControl(@Nullable String className, @Nullable DescriptionMatcherFactory descriptionMatcher) {
28 | this(className, null, descriptionMatcher);
29 | }
30 |
31 | public FormControl(@Nullable String className, @Nullable TextMatcherFactory textMatcher, @Nullable DescriptionMatcherFactory descriptionMatcher) {
32 | mClassName = className;
33 | mTextMatcher = textMatcher;
34 | mDescriptionMatcher = descriptionMatcher;
35 | }
36 |
37 | public FormControl set(int index, @NonNull FormControl child) {
38 | set(index, true, child);
39 | return this;
40 | }
41 |
42 | public FormControl set(int index, boolean essential, @NonNull FormControl child) {
43 | if (index < 0) {
44 | throw new IndexOutOfBoundsException("index=" + index);
45 | }
46 | if (index > 20) {
47 | throw new IndexOutOfBoundsException("index>20; index=" + index);
48 | }
49 | if (null == child) {
50 | throw new NullPointerException("child");
51 | }
52 | if (index < mChilds.size()) {
53 | throw new ArrayStoreException();
54 | }
55 | child.mEssential = essential;
56 | for (int i = mChilds.size(); i < index + 1; i++) {
57 | mChilds.add(null);
58 | }
59 | mChilds.set(index, child);
60 | return this;
61 | }
62 |
63 | public FormControl includeText(@NonNull String text) {
64 | if (null == text) {
65 | throw new NullPointerException("text");
66 | }
67 | if (Macro.Debug) {
68 | if (mIncludeTexts.contains(text)) {
69 | throw new ArrayStoreException("代码错误");
70 | }
71 | if (mExcludeTexts.contains(text)) {
72 | throw new ArrayStoreException("代码错误");
73 | }
74 | }
75 | mIncludeTexts.add(text);
76 | return this;
77 | }
78 |
79 | public FormControl excludeText(@NonNull String text) {
80 | if (null == text) {
81 | throw new NullPointerException("text");
82 | }
83 | if (Macro.Debug) {
84 | if (mIncludeTexts.contains(text)) {
85 | throw new ArrayStoreException("代码错误");
86 | }
87 | if (mExcludeTexts.contains(text)) {
88 | throw new ArrayStoreException("代码错误");
89 | }
90 | }
91 | mExcludeTexts.add(text);
92 | return this;
93 | }
94 |
95 | public void check(@NonNull UiNode uiNode) throws CheckFormResult {
96 | if (null == uiNode) {
97 | throw new NullPointerException("uiNode");
98 | }
99 | uiNode.setFormIndex(0);
100 | check(new int[]{0}, uiNode);
101 | }
102 |
103 | private void check(@NonNull int[] path, @NonNull UiNode uiNode) throws CheckFormResult {
104 | if (null == path) {
105 | throw new NullPointerException("path");
106 | }
107 | if (null == uiNode) {
108 | throw new NullPointerException("uiNode");
109 | }
110 | if (null == mClassName) {
111 | throw new CheckFormResult(path, "当前不应该存在控件");
112 | }
113 | if (!mClassName.equals(uiNode.getClassName())) {
114 | throw new CheckFormResult(path, "类型不匹配");
115 | }
116 | if (null != mTextMatcher) {
117 | if (!mTextMatcher.onMatch(uiNode.getText())) {
118 | throw new CheckFormResult(path, "文本不匹配");
119 | }
120 | }
121 | if (null != mDescriptionMatcher) {
122 | if (!mDescriptionMatcher.onMatch(uiNode.getDescription())) {
123 | throw new CheckFormResult(path, "描述不匹配");
124 | }
125 | }
126 | for (String text : mIncludeTexts) {
127 | if (uiNode.findChildsByText(text).isEmpty()) {
128 | throw new CheckFormResult(path, "没有找到必须的文本");
129 | }
130 | }
131 | for (String text : mExcludeTexts) {
132 | if (!uiNode.findChildsByText(text).isEmpty()) {
133 | throw new CheckFormResult(path, "含有需要排除的文本");
134 | }
135 | }
136 | int realIndex = 0;
137 | for (int i = 0; i < mChilds.size(); i++) {
138 | FormControl childControl = mChilds.get(i);
139 | if (null == childControl) {
140 | realIndex++;
141 | } else {
142 | int[] childPath = new int[path.length + 1];
143 | System.arraycopy(path, 0, childPath, 0, path.length);
144 | childPath[path.length] = i;
145 | if (realIndex >= uiNode.getChildCount()) {
146 | if (null != childControl.mClassName && childControl.mEssential) {
147 | throw new CheckFormResult(childPath, "未找到结点");
148 | }
149 | } else {
150 | UiNode childUiNode = uiNode.getChild(realIndex);
151 | try {
152 | childControl.check(childPath, childUiNode);
153 | childUiNode.setFormIndex(i);
154 | realIndex++;
155 | } catch (CheckFormResult e) {
156 | if (childControl.mEssential) {
157 | throw e;
158 | }
159 | }
160 | }
161 | }
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/libs/appanalyst/FormTree.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | public class FormTree {
10 | private final String mFormPage;
11 | private final String mClassName;
12 | private final FormControl mControl;
13 | private final Map mControlPaths = new HashMap<>();
14 |
15 | public FormTree(@NonNull String formPage, @NonNull String className, @NonNull FormControl control) {
16 | if (null == formPage) {
17 | throw new NullPointerException("formPage");
18 | }
19 | if (null == className) {
20 | throw new NullPointerException("className");
21 | }
22 | if (null == control) {
23 | throw new NullPointerException("control");
24 | }
25 | mFormPage = formPage;
26 | mClassName = className;
27 | mControl = control;
28 | }
29 |
30 | @NonNull
31 | public String getClassName() {
32 | return mClassName;
33 | }
34 |
35 | @NonNull
36 | public String getFormPage() {
37 | return mFormPage;
38 | }
39 |
40 | @NonNull
41 | public FormControl getControl() {
42 | return mControl;
43 | }
44 |
45 | public void addControlPath(@NonNull String pathName, @NonNull int[] path) {
46 | if (null == pathName) {
47 | throw new NullPointerException("pathName");
48 | }
49 | if (null == path) {
50 | throw new NullPointerException("path");
51 | }
52 | mControlPaths.put(pathName, path);
53 | }
54 |
55 | @Nullable
56 | public int[] getControlPath(@NonNull String pathName) {
57 | if (null == pathName) {
58 | throw new NullPointerException("pathName");
59 | }
60 | return mControlPaths.get(pathName);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/libs/appanalyst/Gps.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class Gps {
4 | public float mLatitude;
5 | public float mLongitude;
6 |
7 | public Gps(float latitude, float longitude) {
8 | mLatitude = latitude;
9 | mLongitude = longitude;
10 | }
11 |
12 | public Gps(Gps gps) {
13 | mLatitude = gps.mLatitude;
14 | mLongitude = gps.mLongitude;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/libs/appanalyst/GpsMocker.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.content.Context;
4 | import android.location.Location;
5 | import android.location.LocationListener;
6 | import android.location.LocationManager;
7 | import android.location.LocationProvider;
8 | import android.os.Bundle;
9 | import android.os.Handler;
10 | import android.os.SystemClock;
11 | import android.support.annotation.NonNull;
12 |
13 | public class GpsMocker {
14 | private Gps[] mMockGps = new Gps[]{null};
15 |
16 | public void init(@NonNull Context context) {
17 | if (null == context) {
18 | throw new NullPointerException("context");
19 | }
20 | if (Macro.RealGps) {
21 | return;
22 | }
23 | LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
24 | locationManager.addTestProvider(LocationManager.GPS_PROVIDER,
25 | false, true, true, false, true, true, true,
26 | android.location.Criteria.POWER_HIGH,
27 | android.location.Criteria.ACCURACY_FINE);
28 | locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
29 | locationManager.setTestProviderStatus(LocationManager.GPS_PROVIDER, LocationProvider.AVAILABLE, null, System.currentTimeMillis());
30 | locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, new LocationListener() {
31 | @Override
32 | public void onLocationChanged(Location location) {
33 | }
34 |
35 | @Override
36 | public void onStatusChanged(String provider, int status, Bundle extras) {
37 | }
38 |
39 | @Override
40 | public void onProviderEnabled(String provider) {
41 | }
42 |
43 | @Override
44 | public void onProviderDisabled(String provider) {
45 | }
46 | }
47 | );
48 | }
49 |
50 | public void start(@NonNull Context context, @NonNull Gps gps) {
51 | synchronized (mMockGps) {
52 | mMockGps[0] = gps;
53 | }
54 | scheduleMockGps(context);
55 | }
56 |
57 | public boolean isWorking() {
58 | synchronized (mMockGps) {
59 | return null != mMockGps[0];
60 | }
61 | }
62 |
63 | private void scheduleMockGps(final Context context) {
64 | Gps gps;
65 | synchronized (mMockGps) {
66 | gps = mMockGps[0];
67 | }
68 | if (null == gps) {
69 | return;
70 | }
71 | if (!Macro.RealGps) {
72 | LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
73 | Location location = new Location(LocationManager.GPS_PROVIDER);
74 | location.setLatitude(gps.mLatitude);
75 | location.setLongitude(gps.mLongitude);
76 | location.setAltitude(0);
77 | location.setBearing(0);
78 | location.setSpeed(0);
79 | location.setAccuracy(2);
80 | location.setTime(System.currentTimeMillis());
81 | location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
82 | locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, location);
83 | }
84 | new Handler(context.getMainLooper()).postDelayed(new Runnable() {
85 | @Override
86 | public void run() {
87 | scheduleMockGps(context);
88 | }
89 | }, 1000);
90 | }
91 |
92 | public void stop() {
93 | synchronized (mMockGps) {
94 | mMockGps[0] = null;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/libs/appanalyst/Log.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import android.support.annotation.NonNull;
8 | import android.support.annotation.Nullable;
9 | import android.widget.Toast;
10 |
11 | import java.io.File;
12 | import java.io.FileOutputStream;
13 | import java.io.IOException;
14 | import java.io.PrintWriter;
15 | import java.io.StringWriter;
16 | import java.text.SimpleDateFormat;
17 | import java.util.Date;
18 |
19 | public class Log {
20 | private static String mAppName;
21 | private static FileOutputStream mOuputStream;
22 |
23 | private interface LogOutput {
24 | void output(String tag, String message);
25 | }
26 |
27 | public static void init(String appName) {
28 | if (null == appName) {
29 | throw new NullPointerException("appName");
30 | }
31 | mAppName = appName;
32 | }
33 |
34 | public static void open() {
35 | if (null == mAppName) {
36 | throw new NullPointerException("mAppName");
37 | }
38 | if (null != mOuputStream) {
39 | try {
40 | mOuputStream.close();
41 | } catch (IOException e) {
42 | e.printStackTrace();
43 | }
44 | mOuputStream = null;
45 | }
46 | File file = null;
47 | for (int i = 0; i < 5; i++) {
48 | file = new File(Environment.getExternalStorageDirectory(), mAppName + "/logs/" + new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + ".txt");
49 | if (!file.exists()) {
50 | break;
51 | }
52 | }
53 | if (null != file && !file.exists()) {
54 | try {
55 | file.getParentFile().mkdirs();
56 | file.createNewFile();
57 | mOuputStream = new FileOutputStream(file);
58 | } catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 | }
62 | }
63 |
64 | public static void close() {
65 | if (null != mOuputStream) {
66 | try {
67 | mOuputStream.close();
68 | } catch (IOException e) {
69 | e.printStackTrace();
70 | }
71 | mOuputStream = null;
72 | }
73 | }
74 |
75 | public static void d(@NonNull String message) {
76 | d(message, null, null);
77 | }
78 |
79 | public static void i(@NonNull String message) {
80 | i(message, null, null);
81 | }
82 |
83 | public static void w(@NonNull String message) {
84 | w(message, null, null);
85 | }
86 |
87 | public static void e(@NonNull String message) {
88 | e(message, null, null);
89 | }
90 |
91 | public static void e(@NonNull Throwable throwable) {
92 | e(throwable, null, null);
93 | }
94 |
95 | public static void d(@NonNull String message, Context context, @Nullable ToastDuration toastDuration) {
96 | outputLog(new LogOutput() {
97 | @Override
98 | public void output(String tag, String message) {
99 | android.util.Log.d(tag, message);
100 | }
101 | }, message);
102 | outputFileLog("调试", message);
103 | if (null != toastDuration) {
104 | showToast(message, context, toastDuration.value());
105 | }
106 | }
107 |
108 | public static void i(@NonNull String message, Context context, @Nullable ToastDuration toastDuration) {
109 | outputLog(new LogOutput() {
110 | @Override
111 | public void output(String tag, String message) {
112 | android.util.Log.i(tag, message);
113 | }
114 | }, message);
115 | outputFileLog("信息", message);
116 | if (null != toastDuration) {
117 | showToast(message, context, toastDuration.value());
118 | }
119 | }
120 |
121 | public static void w(@NonNull String message, Context context, @Nullable ToastDuration toastDuration) {
122 | outputLog(new LogOutput() {
123 | @Override
124 | public void output(String tag, String message) {
125 | android.util.Log.w(tag, message);
126 | }
127 | }, message);
128 | outputFileLog("警告", message);
129 | if (null != toastDuration) {
130 | showToast(message, context, toastDuration.value());
131 | }
132 | }
133 |
134 | public static void e(@NonNull String message, Context context, @Nullable ToastDuration toastDuration) {
135 | outputLog(new LogOutput() {
136 | @Override
137 | public void output(String tag, String message) {
138 | android.util.Log.e(tag, message);
139 | }
140 | }, message);
141 | outputFileLog("错误", message);
142 | if (null != toastDuration) {
143 | showToast(message, context, toastDuration.value());
144 | }
145 | }
146 |
147 | public static void e(@NonNull Throwable throwable, Context context, @Nullable ToastDuration toastDuration) {
148 | StringWriter writer = new StringWriter();
149 | PrintWriter printWriter = new PrintWriter(writer);
150 | throwable.printStackTrace(printWriter);
151 | String text = writer.toString();
152 | outputLog(new LogOutput() {
153 | @Override
154 | public void output(String tag, String message) {
155 | android.util.Log.e(tag, message);
156 | }
157 | }, text);
158 | outputFileLog("错误", text);
159 | if (null != toastDuration) {
160 | String message = throwable.getMessage();
161 | if (null != message) {
162 | showToast(message, context, toastDuration.value());
163 | }
164 | }
165 | }
166 |
167 | private static void outputLog(@NonNull LogOutput logOutput, @NonNull String message) {
168 | if (null == logOutput) {
169 | throw new NullPointerException("logOutput");
170 | }
171 | if (null == message) {
172 | throw new NullPointerException("message");
173 | }
174 | if (null == mAppName) {
175 | throw new NullPointerException("mAppName");
176 | }
177 | String texts[] = message.split("\\n");
178 | String log = "";
179 | int n = 0;
180 | for (String text : texts) {
181 | if ("".equals(log)) {
182 | log = text;
183 | } else {
184 | if (log.length() + text.length() > 3800) {
185 | logOutput.output(mAppName, log);
186 | n++;
187 | log = "第" + (n + 1) + "部分: \n" + text;
188 | } else {
189 | if (!"".equals(log)) {
190 | log += "\n";
191 | }
192 | log += text;
193 | }
194 | }
195 | }
196 | logOutput.output(mAppName, log);
197 | }
198 |
199 | private static void outputFileLog(@NonNull String type, @NonNull String message) {
200 | if (null == type) {
201 | throw new NullPointerException("type");
202 | }
203 | if (null == message) {
204 | throw new NullPointerException("message");
205 | }
206 | if (null != mOuputStream) {
207 | synchronized (mOuputStream) {
208 | String text = new SimpleDateFormat("yyyyMMdd,HHmmss,SSS").format(new Date()) + "|" + type + ": " + message + "\n";
209 | try {
210 | mOuputStream.write(text.getBytes());
211 | mOuputStream.flush();
212 | } catch (IOException e) {
213 | e.printStackTrace();
214 | }
215 | }
216 | }
217 | }
218 |
219 | private static void showToast(@NonNull final String message, final Context context, final int duration) {
220 | if (null == message) {
221 | throw new NullPointerException("message");
222 | }
223 | if (null == mAppName) {
224 | throw new NullPointerException("mAppName");
225 | }
226 | if (Looper.myLooper() == context.getMainLooper()) {
227 | Toast.makeText(context, mAppName + ": " + message, duration).show();
228 | } else {
229 | new Handler(context.getMainLooper()).post(new Runnable() {
230 | @Override
231 | public void run() {
232 | Toast.makeText(context, mAppName + ": " + message, duration).show();
233 | }
234 | });
235 | }
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/libs/appanalyst/Macro.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class Macro {
4 | public static final boolean Debug = true;
5 | public static final boolean Network = false;
6 | public static final boolean RealGps = true;
7 | //public static final Gps MockGps = new Gps(30.252497f, 120.169061f);//杭州
8 | //public static final Gps MockGps = new Gps(30.661398f,104.075244f);//成都
9 | public static final Gps MockGps = new Gps(39.726922f, 116.030136f);//北京郊区
10 | //public static final Gps MockGps = new Gps(31.409741f,90.027551f);//西藏山区
11 | }
12 |
--------------------------------------------------------------------------------
/libs/appanalyst/MyException.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class MyException extends Exception {
4 | public MyException() {
5 | super();
6 | }
7 |
8 | public MyException(String detailMessage) {
9 | super(detailMessage);
10 | }
11 |
12 | public MyException(String detailMessage, Throwable throwable) {
13 | super(detailMessage, throwable);
14 | }
15 |
16 | public MyException(Throwable throwable) {
17 | super(throwable);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libs/appanalyst/MyProcess.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import java.io.OutputStream;
4 |
5 | public class MyProcess {
6 | private final Process mProcess;
7 |
8 | public MyProcess(String name) throws MyException {
9 | try {
10 | mProcess = Runtime.getRuntime().exec(name);
11 | } catch (Exception e) {
12 | throw new MyException(e);
13 | }
14 | }
15 |
16 | public void exec(String cmd) throws MyException {
17 | OutputStream outputStream = mProcess.getOutputStream();
18 | try {
19 | outputStream.write((cmd + "\n").getBytes());
20 | outputStream.flush();
21 | } catch (Exception e) {
22 | throw new MyException(e);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/libs/appanalyst/PointXY.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class PointXY {
4 | public int mX;
5 | public int mY;
6 |
7 | public PointXY(int x, int y) {
8 | mX = x;
9 | mY = y;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/appanalyst/PointXY2XY.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | public class PointXY2XY {
4 | public PointXY mFrom;
5 | public PointXY mTo;
6 |
7 | public PointXY2XY(PointXY from, PointXY to) {
8 | mFrom = from;
9 | mTo = to;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/appanalyst/StringUtil.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | public class StringUtil {
7 | @NonNull
8 | public static String toEmptiable(@Nullable CharSequence text) {
9 | if (null == text) {
10 | return "";
11 | }
12 | return toEmptiable(text.toString());
13 | }
14 |
15 | @NonNull
16 | public static String toEb(@Nullable CharSequence text) {
17 | return toEmptiable(text);
18 | }
19 |
20 | @NonNull
21 | public static String toEmptiable(@Nullable String text) {
22 | if (null == text) {
23 | return "";
24 | }
25 | return text;
26 | }
27 |
28 | @NonNull
29 | public static String toEb(@Nullable String text) {
30 | return toEmptiable(text);
31 | }
32 |
33 | @Nullable
34 | public static String toNullable(@Nullable CharSequence text) {
35 | if (null == text) {
36 | return null;
37 | }
38 | return text.toString();
39 | }
40 |
41 | @Nullable
42 | public static String toNb(@Nullable CharSequence text) {
43 | return toNullable(text);
44 | }
45 |
46 | public static boolean compareEmptiable(@Nullable CharSequence left, @Nullable CharSequence right) {
47 | return compareEmptiable(null == left ? null : left.toString(), null == right ? null : right.toString());
48 | }
49 |
50 | public static boolean compareEb(@Nullable CharSequence left, @Nullable CharSequence right) {
51 | return compareEmptiable(left, right);
52 | }
53 |
54 | public static boolean compareEmptiable(@Nullable String left, @Nullable CharSequence right) {
55 | return compareEmptiable(left, null == right ? null : right.toString());
56 | }
57 |
58 | public static boolean compareEb(@Nullable String left, @Nullable CharSequence right) {
59 | return compareEmptiable(left, right);
60 | }
61 |
62 | public static boolean compareEmptiable(@Nullable CharSequence left, @Nullable String right) {
63 | return compareEmptiable(null == left ? null : left.toString(), right);
64 | }
65 |
66 | public static boolean compareEb(@Nullable CharSequence left, @Nullable String right) {
67 | return compareEmptiable(left, right);
68 | }
69 |
70 | public static boolean compareEmptiable(@Nullable String left, @Nullable String right) {
71 | return toEmptiable(left).equals(toEmptiable(right));
72 | }
73 |
74 | public static boolean compareEb(@Nullable String left, @Nullable String right) {
75 | return compareEmptiable(left, right);
76 | }
77 |
78 | public static boolean compareNullable(@Nullable CharSequence left, @Nullable CharSequence right) {
79 | return compareNullable(null == left ? null : left.toString(), null == right ? null : right.toString());
80 | }
81 |
82 | public static boolean compareNb(@Nullable CharSequence left, @Nullable CharSequence right) {
83 | return compareNullable(left, right);
84 | }
85 |
86 | public static boolean compareNullable(@Nullable String left, @Nullable CharSequence right) {
87 | return compareNullable(left, null == right ? null : right.toString());
88 | }
89 |
90 | public static boolean compareNb(@Nullable String left, @Nullable CharSequence right) {
91 | return compareNullable(left, right);
92 | }
93 |
94 | public static boolean compareNullable(@Nullable CharSequence left, @Nullable String right) {
95 | return compareNullable(null == left ? null : left.toString(), right);
96 | }
97 |
98 | public static boolean compareNullable(@Nullable String left, @Nullable String right) {
99 | if (null == left) {
100 | return null == right;
101 | }
102 | if (null == right) {
103 | return null == left;
104 | }
105 | return left.equals(right);
106 | }
107 |
108 | public static boolean compareNb(@Nullable String left, @Nullable String right) {
109 | return compareNullable(left, right);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/libs/appanalyst/TextMatcher.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | public class TextMatcher extends TextMatcherFactory {
6 | public final String mText;
7 |
8 | public TextMatcher(@Nullable String text) {
9 | mText = text;
10 | }
11 |
12 | @Override
13 | public boolean onMatch(@Nullable String text) {
14 | return StringUtil.compareNb(mText, text);
15 | }
16 | }
--------------------------------------------------------------------------------
/libs/appanalyst/TextMatcherFactory.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | public abstract class TextMatcherFactory {
6 | public abstract boolean onMatch(@Nullable String text);
7 | }
--------------------------------------------------------------------------------
/libs/appanalyst/ToastDuration.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.widget.Toast;
4 |
5 | public enum ToastDuration {
6 | LENGTH_SHORT(Toast.LENGTH_SHORT),
7 | LENGTH_LONG(Toast.LENGTH_LONG);
8 |
9 | private int mValue;
10 |
11 | ToastDuration(int value) {
12 | mValue = value;
13 | }
14 |
15 | public int value() {
16 | return mValue;
17 | }
18 | }
--------------------------------------------------------------------------------
/libs/appanalyst/UiNode.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.graphics.Rect;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import android.view.accessibility.AccessibilityNodeInfo;
11 |
12 | import java.util.ArrayList;
13 | import java.util.Iterator;
14 | import java.util.List;
15 |
16 | public class UiNode {
17 | private AccessibilityNodeInfo mAccessibilityNodeInfo;
18 | private String mClassName;
19 | private String mText;
20 | private String mDescription;
21 | private final ArrayList mChilds = new ArrayList<>();
22 | private int mFormIndex = -1;
23 |
24 | public static class NullChildAccessibilityNodeInfoException extends MyException {
25 | public NullChildAccessibilityNodeInfoException() {
26 | super();
27 | }
28 |
29 | public NullChildAccessibilityNodeInfoException(String detailMessage) {
30 | super(detailMessage);
31 | }
32 |
33 | public NullChildAccessibilityNodeInfoException(String detailMessage, Throwable throwable) {
34 | super(detailMessage, throwable);
35 | }
36 |
37 | public NullChildAccessibilityNodeInfoException(Throwable throwable) {
38 | super(throwable);
39 | }
40 | }
41 |
42 | public UiNode(@NonNull AccessibilityNodeInfo accessibilityNodeInfo) throws NullChildAccessibilityNodeInfoException, MyException {
43 | if (null == accessibilityNodeInfo) {
44 | throw new NullPointerException("accessibilityNodeInfo");
45 | }
46 | try {
47 | mAccessibilityNodeInfo = accessibilityNodeInfo;
48 | mClassName = StringUtil.toEb(accessibilityNodeInfo.getClassName());
49 | mText = StringUtil.toNb(accessibilityNodeInfo.getText());
50 | mDescription = StringUtil.toNb(accessibilityNodeInfo.getContentDescription());
51 | int count = accessibilityNodeInfo.getChildCount();
52 | for (int i = 0; i < count; i++) {
53 | AccessibilityNodeInfo childAccessibilityNodeInfo = accessibilityNodeInfo.getChild(i);
54 | if (null == childAccessibilityNodeInfo) {
55 | throw new NullChildAccessibilityNodeInfoException("出现空的子AccessibilityNodeInfo");
56 | }
57 | mChilds.add(new UiNode(childAccessibilityNodeInfo));
58 | }
59 | } catch (NullChildAccessibilityNodeInfoException e) {
60 | throw e;
61 | } catch (MyException e) {
62 | throw e;
63 | } catch (Exception e) {
64 | throw new MyException(e);
65 | }
66 | }
67 |
68 | public void recycle() {
69 | for (UiNode childNode : mChilds) {
70 | childNode.recycle();
71 | }
72 | if (null != mAccessibilityNodeInfo) {
73 | mAccessibilityNodeInfo.recycle();
74 | }
75 | }
76 |
77 | @NonNull
78 | public String getClassName() {
79 | return mClassName;
80 | }
81 |
82 | @Nullable
83 | public String getText() {
84 | return mText;
85 | }
86 |
87 | @Nullable
88 | public String getDescription() {
89 | return mDescription;
90 | }
91 |
92 | @NonNull
93 | public Iterator childIterator() {
94 | return mChilds.iterator();
95 | }
96 |
97 | public int getChildCount() {
98 | return mChilds.size();
99 | }
100 |
101 | @Nullable
102 | public UiNode getChild(int index) {
103 | try {
104 | return mChilds.get(index);
105 | } catch (Exception e) {
106 | return null;
107 | }
108 | }
109 |
110 | public void setFormIndex(int index) {
111 | mFormIndex = index;
112 | }
113 |
114 | public int getFormIndex() {
115 | return mFormIndex;
116 | }
117 |
118 | @Nullable
119 | public List findChildsByText(@NonNull String text) {
120 | if (null == text) {
121 | throw new NullPointerException("text");
122 | }
123 | List childs = new ArrayList<>();
124 | findChildsByText(childs, this, text);
125 | return childs;
126 | }
127 |
128 | private static void findChildsByText(@NonNull List childs, @NonNull UiNode uiNode, @NonNull String text) {
129 | if (null == childs) {
130 | throw new NullPointerException("childs");
131 | }
132 | if (null == uiNode) {
133 | throw new NullPointerException("uiNode");
134 | }
135 | if (null == text) {
136 | throw new NullPointerException("text");
137 | }
138 | if (uiNode.getText().equals(text)) {
139 | childs.add(uiNode);
140 | }
141 | for (UiNode child : uiNode.mChilds) {
142 | child.findChildsByText(childs, child, text);
143 | }
144 | }
145 |
146 | @Nullable
147 | public UiNode getNode(@NonNull int[] path) throws MyException {
148 | return getNode(path, 0);
149 | }
150 |
151 | @Nullable
152 | private UiNode getNode(@NonNull int[] path, int level) throws MyException {
153 | if (null == path) {
154 | throw new NullPointerException("path");
155 | }
156 | if (path.length <= level) {
157 | throw new MyException("路径表示异常");
158 | }
159 | if (0 == level) {
160 | if (0 != path[0]) {
161 | throw new MyException("路径表示错误");
162 | }
163 | }
164 | if (path.length == level + 1) {
165 | return this;
166 | }
167 | int index = path[level + 1];
168 | for (UiNode child : mChilds) {
169 | if (child.getFormIndex() == index) {
170 | return child.getNode(path, level + 1);
171 | }
172 | }
173 | return null;
174 | }
175 |
176 | public void click() throws MyException {
177 | try {
178 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
179 | } catch (Exception e) {
180 | throw new MyException(e);
181 | }
182 | }
183 |
184 | public void longClick() throws MyException {
185 | try {
186 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
187 | } catch (Exception e) {
188 | throw new MyException(e);
189 | }
190 | }
191 |
192 | public void setEditText(@NonNull Context context, @NonNull String text) throws MyException {
193 | if (null == context) {
194 | throw new NullPointerException("context");
195 | }
196 | if (null == text) {
197 | throw new NullPointerException("text");
198 | }
199 | try {
200 | //清空内容
201 | Bundle arguments = new Bundle();
202 | arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, AccessibilityNodeInfo
203 | .MOVEMENT_GRANULARITY_LINE);
204 | arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
205 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
206 | arguments);
207 | //粘贴内容
208 | ClipData clipData = ClipData.newPlainText(context.getPackageName(), text);
209 | ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
210 | clipboardManager.setPrimaryClip(clipData);
211 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);
212 | } catch (Exception e) {
213 | throw new MyException(e);
214 | }
215 | }
216 |
217 | public void scrollForward() throws MyException {
218 | try {
219 | mAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
220 | } catch (Exception e) {
221 | throw new MyException(e);
222 | }
223 | }
224 |
225 | public void getBoundsInScreen(Rect bounds) throws MyException {
226 | try {
227 | mAccessibilityNodeInfo.getBoundsInScreen(bounds);
228 | } catch (Exception e) {
229 | throw new MyException(e);
230 | }
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/libs/appanalyst/UiTree.java:
--------------------------------------------------------------------------------
1 | package silence.com.cn.a310application.appanalyst;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import android.view.accessibility.AccessibilityNodeInfo;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class UiTree {
11 | public static final List PRESET_FORM = new ArrayList<>();
12 |
13 | private final String mClassName;
14 | private final UiNode mNode;
15 | private final FormTree mFoundForm;
16 | private final String mLog;
17 |
18 | public UiTree(@NonNull String className, @Nullable AccessibilityNodeInfo accessibilityNodeInfo) throws UiNode.NullChildAccessibilityNodeInfoException, MyException {
19 | if (null == className) {
20 | throw new NullPointerException("className");
21 | }
22 | mClassName = className;
23 | mNode = (null == accessibilityNodeInfo ? null : new UiNode(getRootAccessibilityNodeInfo(accessibilityNodeInfo)));
24 | mFoundForm = findForm();
25 | mLog = obtainLog();
26 | }
27 |
28 | public static AccessibilityNodeInfo getRootAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo accessibilityNodeInfo) throws MyException {
29 | if (null == accessibilityNodeInfo) {
30 | throw new NullPointerException("accessibilityNodeInfo");
31 | }
32 | AccessibilityNodeInfo current = null;
33 | try {
34 | current = accessibilityNodeInfo;
35 | while (true) {
36 | AccessibilityNodeInfo parent = current.getParent();
37 | if (null == parent) {
38 | return current;
39 | }
40 | current = parent;
41 | }
42 | } catch (Exception e) {
43 | if (null != current) {
44 | current.recycle();
45 | }
46 | throw new MyException(e);
47 | }
48 | }
49 |
50 | public void recycle() {
51 | if (null != mNode) {
52 | mNode.recycle();
53 | }
54 | }
55 |
56 | @NonNull
57 | public String getClassName() {
58 | return mClassName;
59 | }
60 |
61 | @NonNull
62 | public UiNode getNode() {
63 | return mNode;
64 | }
65 |
66 | @Nullable
67 | public FormTree getFoundForm() {
68 | return mFoundForm;
69 | }
70 |
71 | @NonNull
72 | public String getFoundFormPage() {
73 | return null == mFoundForm ? "" : mFoundForm.getFormPage();
74 | }
75 |
76 | @NonNull
77 | public UiNode getNode(@NonNull String pathName) throws MyException {
78 | if (null == pathName) {
79 | throw new NullPointerException("pathName");
80 | }
81 | if (null == mNode) {
82 | throw new MyException("没有结点");
83 | }
84 | if (null == mFoundForm) {
85 | throw new MyException("没有匹配的Form");
86 | }
87 | int[] path = mFoundForm.getControlPath(pathName);
88 | if (null == path) {
89 | throw new MyException("没有匹配的路径名");
90 | }
91 | UiNode node;
92 | try {
93 | node = mNode.getNode(path);
94 | } catch (MyException e) {
95 | throw new MyException("获取结点异常", e);
96 | }
97 | if (null == node) {
98 | throw new MyException("没有找到结点");
99 | }
100 | return node;
101 | }
102 |
103 | @NonNull
104 | public FormTree findForm() {
105 | for (FormTree formTree : PRESET_FORM) {
106 | if (mClassName.equals(formTree.getClassName())) {
107 | if (null != mNode) {
108 | try {
109 | formTree.getControl().check(mNode);
110 | return formTree;
111 | } catch (CheckFormResult e) {
112 | int path[] = e.getPath();
113 | String text = "检查 # " + formTree.getFormPage() + "\n[";
114 | for (int i = 0; i < path.length; i++) {
115 | if (0 != i) {
116 | text += ",";
117 | }
118 | text += i;
119 | }
120 | text += "]\n[";
121 | for (int i = 0; i < path.length; i++) {
122 | if (0 != i) {
123 | text += ",";
124 | }
125 | text += path[i];
126 | }
127 | text += "]\n: " + e.getMessage();
128 | Log.e(text);
129 | }
130 | }
131 | }
132 | }
133 | return null;
134 | }
135 |
136 | @NonNull
137 | public String getLog() {
138 | return mLog;
139 | }
140 |
141 | @NonNull
142 | public String obtainLog() {
143 | String log = "控件树:ClassName=" + mClassName + "\n";
144 | if (null == mNode) {
145 | return log + "空";
146 | } else {
147 | return log + obtainChildLog(0, 0, mNode);
148 | }
149 | }
150 |
151 | private String obtainChildLog(int level, int index, UiNode uiNode) {
152 | if (null == uiNode) {
153 | return "";
154 | }
155 | String log = "" + level + "[" + index + "]";
156 | for (int i = 0; i <= level; i++) {
157 | log += "---";
158 | }
159 | log += uiNode.getClassName() + "|" + uiNode.getText() + "|" + uiNode.getDescription();
160 | for (int i = 0; i < uiNode.getChildCount(); i++) {
161 | String childLog = obtainChildLog(level + 1, i, uiNode.getChild(i));
162 | if (!"".equals(childLog)) {
163 | log += "\n" + childLog;
164 | }
165 | }
166 | return log;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------