├── .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 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 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 |