瀏覽代碼

第一次提交

yangyang 11 月之前
當前提交
ecaa43700c
共有 100 個文件被更改,包括 22478 次插入0 次删除
  1. 80 0
      .gitignore
  2. 66 0
      LICENSE
  3. 165 0
      README.md
  4. 18 0
      app/.gitignore
  5. 二進制
      app/Keystore.jks
  6. 61 0
      app/build.gradle
  7. 二進制
      app/libs/SaaS_TalkingDataSDK_Android_V5.0.8.jar
  8. 二進制
      app/libs/lib_wwapi-2.0.12.11.aar
  9. 57 0
      app/proguard-rules.pro
  10. 124 0
      app/src/main/AndroidManifest.xml
  11. 二進制
      app/src/main/ic_launcher-playstore.png
  12. 179 0
      app/src/main/java/org/yameida/worktool/Constant.kt
  13. 117 0
      app/src/main/java/org/yameida/worktool/Demo.kt
  14. 64 0
      app/src/main/java/org/yameida/worktool/MyApplication.kt
  15. 65 0
      app/src/main/java/org/yameida/worktool/activity/AccessibilityGuideActivity.kt
  16. 26 0
      app/src/main/java/org/yameida/worktool/activity/BrowserActivity.kt
  17. 88 0
      app/src/main/java/org/yameida/worktool/activity/FloatViewGuideActivity.kt
  18. 134 0
      app/src/main/java/org/yameida/worktool/activity/GetScreenShotActivity.kt
  19. 364 0
      app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt
  20. 37 0
      app/src/main/java/org/yameida/worktool/activity/LoginActivity.kt
  21. 235 0
      app/src/main/java/org/yameida/worktool/activity/SettingsActivity.kt
  22. 275 0
      app/src/main/java/org/yameida/worktool/activity/SettingsAdvanceActivity.kt
  23. 16 0
      app/src/main/java/org/yameida/worktool/annotation/RequestMapping.java
  24. 28 0
      app/src/main/java/org/yameida/worktool/config/GlobalException.java
  25. 162 0
      app/src/main/java/org/yameida/worktool/model/AppUpdate.java
  26. 145 0
      app/src/main/java/org/yameida/worktool/model/ExecCallbackBean.kt
  27. 21 0
      app/src/main/java/org/yameida/worktool/model/MyConfigBean.kt
  28. 534 0
      app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java
  29. 93 0
      app/src/main/java/org/yameida/worktool/model/WeworkMessageListBean.kt
  30. 11 0
      app/src/main/java/org/yameida/worktool/model/network/CheckUpdateResult.java
  31. 11 0
      app/src/main/java/org/yameida/worktool/model/network/GetMyConfigResult.java
  32. 11 0
      app/src/main/java/org/yameida/worktool/model/operation/SelectResult.kt
  33. 36 0
      app/src/main/java/org/yameida/worktool/notification/PlayNotifyManager.kt
  34. 162 0
      app/src/main/java/org/yameida/worktool/observer/MultiFileObserver.java
  35. 343 0
      app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt
  36. 310 0
      app/src/main/java/org/yameida/worktool/service/MyLooper.kt
  37. 107 0
      app/src/main/java/org/yameida/worktool/service/PlayNotifyService.kt
  38. 741 0
      app/src/main/java/org/yameida/worktool/service/WeworkController.kt
  39. 554 0
      app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt
  40. 28 0
      app/src/main/java/org/yameida/worktool/service/WeworkInteractionImpl.kt
  41. 1150 0
      app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt
  42. 6409 0
      app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt
  43. 177 0
      app/src/main/java/org/yameida/worktool/service/WeworkService.kt
  44. 43 0
      app/src/main/java/org/yameida/worktool/utils/AccessibilityExtraUtil.kt
  45. 1353 0
      app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt
  46. 46 0
      app/src/main/java/org/yameida/worktool/utils/CacheUtil.kt
  47. 33 0
      app/src/main/java/org/yameida/worktool/utils/DonateUtil.kt
  48. 53 0
      app/src/main/java/org/yameida/worktool/utils/ExtensionMethod.kt
  49. 150 0
      app/src/main/java/org/yameida/worktool/utils/FloatWindowHelper.kt
  50. 96 0
      app/src/main/java/org/yameida/worktool/utils/FlowPermissionHelper.kt
  51. 8 0
      app/src/main/java/org/yameida/worktool/utils/GenericFileProvider.kt
  52. 64 0
      app/src/main/java/org/yameida/worktool/utils/HostTestHelper.kt
  53. 179 0
      app/src/main/java/org/yameida/worktool/utils/HttpUtil.kt
  54. 140 0
      app/src/main/java/org/yameida/worktool/utils/IWWAPIUtil.kt
  55. 26 0
      app/src/main/java/org/yameida/worktool/utils/ImageDepthSizeUtil.java
  56. 1230 0
      app/src/main/java/org/yameida/worktool/utils/ImageInfo.java
  57. 52 0
      app/src/main/java/org/yameida/worktool/utils/LogUtilsInit.kt
  58. 51 0
      app/src/main/java/org/yameida/worktool/utils/OkHttpUtil.kt
  59. 51 0
      app/src/main/java/org/yameida/worktool/utils/PermissionHelper.kt
  60. 225 0
      app/src/main/java/org/yameida/worktool/utils/PermissionPageManagement.java
  61. 41 0
      app/src/main/java/org/yameida/worktool/utils/PropUtil.kt
  62. 24 0
      app/src/main/java/org/yameida/worktool/utils/RegexHelper.kt
  63. 26 0
      app/src/main/java/org/yameida/worktool/utils/RuntimeUtil.kt
  64. 67 0
      app/src/main/java/org/yameida/worktool/utils/ShareCommentsUtil.java
  65. 78 0
      app/src/main/java/org/yameida/worktool/utils/ShareUtil.kt
  66. 33 0
      app/src/main/java/org/yameida/worktool/utils/StringFeatureUtil.java
  67. 19 0
      app/src/main/java/org/yameida/worktool/utils/Views.java
  68. 182 0
      app/src/main/java/org/yameida/worktool/utils/WebSocketManager.java
  69. 434 0
      app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt
  70. 556 0
      app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt
  71. 128 0
      app/src/main/java/org/yameida/worktool/utils/capture/AndroidUtils.kt
  72. 92 0
      app/src/main/java/org/yameida/worktool/utils/capture/BitmapUtil.kt
  73. 103 0
      app/src/main/java/org/yameida/worktool/utils/capture/Color.java
  74. 452 0
      app/src/main/java/org/yameida/worktool/utils/capture/Image.java
  75. 28 0
      app/src/main/java/org/yameida/worktool/utils/capture/MediaProjectionHolder.kt
  76. 42 0
      app/src/main/java/org/yameida/worktool/utils/capture/Point.java
  77. 119 0
      app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtil.java
  78. 142 0
      app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtilByMediaPro.java
  79. 110 0
      app/src/main/java/org/yameida/worktool/utils/envcheck/CheckHook.java
  80. 225 0
      app/src/main/java/org/yameida/worktool/utils/envcheck/CheckRoot.java
  81. 二進制
      app/src/main/res/drawable-xxhdpi/good_morning_img.png
  82. 二進制
      app/src/main/res/drawable-xxhdpi/good_night_img.png
  83. 11 0
      app/src/main/res/drawable/buttonshapewhitebg.xml
  84. 22 0
      app/src/main/res/drawable/comment_red_btn.xml
  85. 9 0
      app/src/main/res/drawable/ic_email_white_24dp.xml
  86. 9 0
      app/src/main/res/drawable/ic_lock_white_24dp.xml
  87. 二進制
      app/src/main/res/font/calibri.ttf
  88. 232 0
      app/src/main/res/layout/activity_accessibility_guide.xml
  89. 16 0
      app/src/main/res/layout/activity_browser.xml
  90. 232 0
      app/src/main/res/layout/activity_float_guide.xml
  91. 365 0
      app/src/main/res/layout/activity_listen.xml
  92. 181 0
      app/src/main/res/layout/activity_login.xml
  93. 666 0
      app/src/main/res/layout/activity_settings.xml
  94. 740 0
      app/src/main/res/layout/activity_settings_advance.xml
  95. 二進制
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  96. 二進制
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  97. 50 0
      app/src/main/res/values/colors-rec.xml
  98. 8 0
      app/src/main/res/values/colors.xml
  99. 32 0
      app/src/main/res/values/dimens.xml
  100. 0 0
      app/src/main/res/values/strings.xml

+ 80 - 0
.gitignore

@@ -0,0 +1,80 @@
+# Built application files
+*.ap_
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+
+# Gradle files
+.gradle/
+build/
+release/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/caches
+
+# Keystore files
+# Uncomment the following line if you do not want to check your keystore files in.
+#*.jks
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+.idea/
+
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.cxx
+/release
+/debug
+build

文件差異過大導致無法顯示
+ 66 - 0
LICENSE


+ 165 - 0
README.md

@@ -0,0 +1,165 @@
+## WorkTool
+
+本安卓应用是一个依附于企业微信来运行的无人值守群管理机器人,非hook非侵入式,无任何修改,使用安卓系统原生支持的无障碍服务能力,手机无需Root,应用兼容99%的手机,长时间运行稳定且因为没有修改系统和软件所以理论上不存在被外挂监测和封号风险。
+机器人支持查看问答记录,创建群、群管理,群发等功能,还支持接入自己的问答接口来全面接管机器人。
+
+## 演示
+
+**发送消息**
+
+<img src="https://github.com/gallonyin/worktool/blob/master/images/send_message.gif"  height="500" width="280">
+注:动图为机器人自动运行
+更多演示看这里
+https://worktool.apifox.cn/doc-840833
+
+
+<img src="https://github.com/gallonyin/worktool/blob/master/images/chatgpt.png"  height="500" width="360">
+机器人集成ChatGPT效果
+
+
+## 兼容版本(重要)
+
+⏬ 经过测试验证的版本:企业微信 4.0.2 至 4.1.10、企业微信政务版
+
+⚠️ 建议使用 WorkTool 已兼容的最新企业微信版本
+
+## 快速使用
+
+💡 [快速入门文档](https://worktool.apifox.cn/)
+
+1. 准备一台无人使用的可联网安卓手机(本APP兼容99%安卓机型 系统要求>=Android7.0)
+2. 手机登录企业微信(账号需要提前实名认证,不然很多功能无法正常使用)
+3. 建议提前给该企业微信账号开通"工作台"-"客户群"权限(如无需外部群创建和管理可不开启)
+4. 自助申请一个[机器人链接号(点击这里)](https://admin.worktool.ymdyes.cn/register),您也可以加入QQ群向管理员咨询如何操作。
+5. 在这台手机上安装[WorkTool APP安装包(点击下载)](https://cdn.asrtts.cn/uploads/worktool/apk/worktool-latest.apk)
+6. 打开WorkTool APP,按照APP提示保存链接号,开启主功能,并打开到企业微信界面,不要关闭屏幕即可。
+
+如果您想使用自己开发的QA回调接口接收机器人收到的所有消息并定制回答,请参考[第三方QA回调接口规范(点击这里)](https://worktool.apifox.cn/doc-861677)开发接口,并在[机器人第三方QA配置(点击这里)](https://worktool.apifox.cn/api-22587884)提交您的机器人id和回调接口地址
+
+## 文档
+
+这里有所有详细的API文档和调用示例!!!
+
+这里有所有详细的API文档和调用示例!!!
+
+这里有所有详细的API文档和调用示例!!!
+
+📝 https://worktool.apifox.cn/doc-850007
+
+## 零代码集成
+
+- 🤗 集简云: https://www.jijyun.cn/apps/detail/1000983
+
+## 混淆
+
+绝大部分代码均可以混淆,但由于使用的类库如okhttp、umeng不能混淆等情况,已经列在proguard-rules.pro当中,可以直接使用
+
+#  Copyright
+
+Apache License, Version 2.0
+
+#  联系方式
+
+- ⭐️ 官网: https://worktool.ymdyes.cn/
+- ⭐️ 合作申请: https://admin.worktool.ymdyes.cn/form/consult (私有化/微信版/协议版/定制)
+- 💻 Email: gallonyin@163.com
+- 👍🏻 QQ群: 492831452、612832531(问题反馈)
+- ❓ 如果遇到问题欢迎在群里提问或提交issue。
+- 🤗 WorkTool志愿者开发群: 716217439
+
+# 版本更新
+
+v2.8.1 2023-11-19 自动通过好友请求开关;执行队列去重算法优化
+
+v2.8.0 2023-10-18 兼容企微4.1.10;其他已知问题优化
+
+v2.7.4 2023-10-15 消息发送优化和准确率提升
+
+v2.7.3 2023-10-12 发送消息优化
+
+v2.7.2 2023-10-10 已知问题修复
+
+v2.7.1 2023-09-11 优化兼容鸿蒙;优化获取群名;其他已知问题修复
+
+<details>
+<summary><b>往期更新</b></summary>
+
+v2.7.0 2023-08-27 兼容企微4.1.9;其他已知问题优化
+
+v2.6.8 2023-08-12 删除联系人;其他已知问题优化
+
+v2.6.7 2023-07-31 修复房间类型错误;其他已知问题优化
+
+v2.6.6 2023-07-25 优化消息接收;其他已知问题优化
+
+v2.6.5 2023-07-14 优化发送文件;其他已知问题优化
+
+v2.6.4 2023-06-28 修复发消息功能缺陷;其他已知问题优化
+
+v2.6.3 2023-06-26 日志文件分享
+
+v2.6.2 2023-06-25 增加防卡顿模式;自动删除已退出群;群信息保存通讯录;消息检查优化;其他已知问题修复
+
+v2.6.1 2023-06-13 控件检索优化;其他已知问题修复
+
+v2.6.0 2023-05-28 anr自动处理;其他已知问题修复
+
+v2.5.9 2023-05-12 自动通过群邀请;拉人进群发送邀请;获取群聊全称;获取群二维码;其他已知问题修复
+
+v2.5.8 2023-04-06 优化消息一致性检查;执行异常自动重试;兼容性更新;其他已知问题修复
+
+v2.5.7 2023-03-15 自动通过群邀请;优化消息识别;异常环境监测;其他已知问题修复
+
+v2.5.6 2023-02-06 兼容主流模拟器;其他已知缺陷修复
+
+v2.5.5 2023-02-02 文件发送优化;新消息增强校验;其他已知缺陷修复
+
+v2.5.4 2023-01-28 文件发送优化;消息列表识别优化;切换企业;其他已知缺陷修复
+
+v2.5.3 2023-01-11 群模板兼容新版;消息类型识别优化;其他已知缺陷修复
+
+v2.5.2 2023-01-05 返回首页缺陷修复
+
+v2.5.1 2023-01-04 优化返回首页和回复速度;支持群二维码回调;其他已知缺陷修复
+
+v2.4.2 2022-12-14 优化at;优化通过好友请求;其他已知缺陷修复
+
+v2.4.1 2022-12-9 集成悬浮窗启停功能;房间检索优化;界面更新;其他已知缺陷修复
+
+v2.4.0 2022-11-23 修改用户备注;添加待办;重要宕机缺陷修复
+
+v2.3.3 2022-10-28 解散群;改群模板;其他已知问题优化
+
+v2.3.1 2022-10-25 优化推送文件;特殊符号兼容;交互提示;其他已知问题修复
+
+v2.3.0 2022-10-17 支持at多人;支持推送任意文件;支持群备注修改;交互提示优化;其他已知问题修复
+
+v2.2.6 2022-09-16 优化搜索
+
+v2.2.5 2022-09-15 主动加好友可改附言;移除[自动回复]前缀;群内回复@提醒;搜索更加精准;学校类企业兼容
+
+v2.2.3 2022-08-26 兼容主动添加好友;文本匹配优化;其他已知问题优化
+
+v2.2.1 2022-08-25 多控件类型兼容;兼容多版本系统;其他已知问题修复
+
+v2.1.2 2022-08-18 多控件类型兼容;兼容多版本系统
+
+v2.1 2022-08-17 真@提醒;获取未读消息优化;其他已知问题修复
+
+v2.0 2022-08-11 全面兼容企业微信最新版本(4.0.12)和政务微信;控件搜索优化;已知问题修复
+
+v1.3 2022-08-02 被动添加好友优化
+
+v1.2 2022-07-11 内部群已读数过滤;避免群名重复创建;可回调获取群二维码;其他稳定性优化
+
+v1.1 2022-06-20 大幅度提高系统稳定性和响应速度
+
+v1.0 2022-05-27 首次可用版本更新
+</details>
+
+## 捐赠 
+
+如果觉得还不错,请作者喝杯咖啡吧 ☺
+
+<img src="https://cdn.asrtts.cn/static/image/%E5%BE%AE%E4%BF%A1%E6%89%93%E8%B5%8F.png"  height="379" width="351">
+

+ 18 - 0
app/.gitignore

@@ -0,0 +1,18 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+/release
+/debug
+/.idea
+build

二進制
app/Keystore.jks


+ 61 - 0
app/build.gradle

@@ -0,0 +1,61 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+    compileSdkVersion 30
+
+    defaultConfig {
+        applicationId "com.rk.worktool"
+        minSdkVersion 24
+        targetSdkVersion 30
+        versionCode 28011
+        versionName "2.8.1"
+    }
+
+    buildTypes {
+        debug {
+            minifyEnabled false
+            //对齐
+            zipAlignEnabled true
+            //移除无用资源
+            shrinkResources false
+            signingConfig signingConfigs.debug
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        release {
+            minifyEnabled true
+            //对齐
+            zipAlignEnabled true
+            //移除无用资源
+            shrinkResources false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    signingConfigs {
+        debug {
+            storeFile file('keystore.jks')
+            storePassword "123456"
+            keyAlias "keystore"
+            keyPassword "123456"
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+    implementation project(':baselibrary')
+    implementation project(':floatwindow')
+    implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
+
+    //友盟统计SDK
+    implementation  'com.umeng.umsdk:common:9.4.7'// 必选
+    implementation  'com.umeng.umsdk:asms:1.4.1'// 必选
+    implementation 'com.umeng.umsdk:apm:1.5.2' // 错误分析升级为独立SDK,看crash数据请一定集成,可选
+}

二進制
app/libs/SaaS_TalkingDataSDK_Android_V5.0.8.jar


二進制
app/libs/lib_wwapi-2.0.12.11.aar


+ 57 - 0
app/proguard-rules.pro

@@ -0,0 +1,57 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+# umeng
+-keep class com.umeng.** {*;}
+
+-keep class org.repackage.** {*;}
+
+-keepclassmembers class * {
+   public <init> (org.json.JSONObject);
+}
+
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+#okhttp
+-dontwarn okhttp3.**
+-keep class okhttp3.**{*;}
+
+#okio
+-dontwarn okio.**
+-keep class okio.**{*;}
+
+#bean
+-dontwarn org.yameida.worktool.model.**
+-keep class org.yameida.worktool.model.**{*;}
+
+#talkingdata
+-dontwarn com.tendcloud.tenddata.**
+-keep class com.tendcloud.** {*;}
+-keep public class com.tendcloud.** {  public protected *;}
+
+#iwwapi
+-keep class com.tencent.wework.api.** {
+   *;
+}

+ 124 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="org.yameida.worktool">
+
+    <queries>
+        <package android:name="com.tencent.wework" />
+    </queries>
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <!--在SDCard中创建与删除文件权限-->
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
+    <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
+        tools:ignore="QueryAllPackagesPermission" />
+
+    <application
+        android:name="org.yameida.worktool.MyApplication"
+        android:allowBackup="true"
+        tools:replace="android:allowBackup"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/network_security_config"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme"
+        android:usesCleartextTraffic="true"
+        android:requestLegacyExternalStorage="true">
+
+        <activity
+            android:name="org.yameida.worktool.activity.ListenActivity"
+            android:windowSoftInputMode="adjustUnspecified|stateHidden"
+            android:launchMode="singleTask"
+            android:theme="@style/AppTheme">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="org.yameida.worktool.activity.LoginActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/AppTheme">
+        </activity>
+        <activity
+            android:name="org.yameida.worktool.activity.SettingsActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/AppTheme">
+        </activity>
+        <activity
+            android:name="org.yameida.worktool.activity.SettingsAdvanceActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/AppTheme">
+        </activity>
+        <activity
+            android:name="org.yameida.worktool.activity.BrowserActivity"
+            android:windowSoftInputMode="adjustUnspecified|stateHidden"
+            android:launchMode="singleTop"
+            android:theme="@style/AppTheme">
+        </activity>
+        <activity
+            android:name="org.yameida.worktool.activity.FloatViewGuideActivity"
+            android:windowSoftInputMode="adjustUnspecified|stateHidden"
+            android:theme="@style/AppTheme">
+        </activity>
+        <activity
+            android:name="org.yameida.worktool.activity.AccessibilityGuideActivity"
+            android:windowSoftInputMode="adjustUnspecified|stateHidden"
+            android:theme="@style/AppTheme">
+        </activity>
+        <activity
+            android:name=".activity.GetScreenShotActivity"
+            android:autoRemoveFromRecents="true"
+            android:excludeFromRecents="true"
+            android:launchMode="singleInstance"
+            android:theme="@style/AppTheme.TranslucentNoTitleFullscreen" />
+        <service
+            android:name="org.yameida.worktool.service.WeworkService"
+            android:enabled="true"
+            android:exported="true"
+            android:label="@string/app_name"
+            android:foregroundServiceType="dataSync"
+            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.accessibilityservice"
+                android:resource="@xml/accessibility_service_config" />
+        </service>
+        <service
+            android:foregroundServiceType="mediaProjection"
+            android:name=".service.PlayNotifyService" />
+
+        <provider
+            android:name=".utils.GenericFileProvider"
+            android:authorities="org.yameida.worktool.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/provider_paths" />
+        </provider>
+
+        <meta-data android:name="TD_APP_ID" android:value="80E9C84E39904DAFB28562910FF7C86C" />
+        <meta-data android:name="TD_CHANNEL_ID" android:value="worktool_master" />
+    </application>
+
+</manifest>

二進制
app/src/main/ic_launcher-playstore.png


+ 179 - 0
app/src/main/java/org/yameida/worktool/Constant.kt

@@ -0,0 +1,179 @@
+package org.yameida.worktool
+
+import com.blankj.utilcode.util.SPUtils
+
+object Constant {
+
+    val AVAILABLE_VERSION = arrayListOf(
+        "4.0.2",
+        "4.0.6",
+        "4.0.8",
+        "4.0.10",
+        "4.0.12",
+        "4.0.16",
+        "4.0.18",
+        "4.0.19",
+        "4.0.20",
+        "4.1.0",
+        "4.1.2",
+        "4.1.3",
+        "4.1.6",
+        "4.1.7",
+        "4.1.8",
+        "4.1.9",
+        "4.1.10"
+    )
+    val AVAILABLE_VERSION_MAP = mapOf(
+        Pair("4.0.2", 40002),
+        Pair("4.0.6", 40006),
+        Pair("4.0.8", 40008),
+        Pair("4.0.10", 40010),
+        Pair("4.0.12", 40012),
+        Pair("4.0.16", 40016),
+        Pair("4.0.18", 40018),
+        Pair("4.0.19", 40019),
+        Pair("4.0.20", 40020),
+        Pair("4.1.0", 40100),
+        Pair("4.1.2", 40102),
+        Pair("4.1.3", 40103),
+        Pair("4.1.6", 40106),
+        Pair("4.1.7", 40107),
+        Pair("4.1.8", 40108),
+        Pair("4.1.9", 40109),
+        Pair("4.1.9", 40110)
+    )
+    const val PACKAGE_NAMES = "com.tencent.wework"
+    const val WEWORK_NOTIFY = "wework_notify"
+    const val BASE_LONG_INTERVAL = 5000L
+    const val BASE_CHANGE_PAGE_INTERVAL = 1000L
+    const val BASE_POP_WINDOW_INTERVAL = 500L
+    var LONG_INTERVAL = BASE_LONG_INTERVAL
+    var CHANGE_PAGE_INTERVAL = BASE_CHANGE_PAGE_INTERVAL
+    var POP_WINDOW_INTERVAL = BASE_POP_WINDOW_INTERVAL
+    private const val DEFAULT_HOST = "wss://assistant.flowbb.top/webSocket/"
+
+    var version = Int.MAX_VALUE
+    var myName = ""
+    var myCorp = ""
+
+    //    var regTrimTitle = "(…$)|(-.*$)|(\\(.*?\\)$)".toRegex()
+    var regMail = "\\S+@\\S+\\.\\S+".toRegex()
+    var regTrimTitle = "(…$)".toRegex()
+    var key = "9876543210abcdef".toByteArray()
+    var iv = "0123456789abcdef".toByteArray()
+    var lastUseMultiSender = 0L
+    val transformation = "AES/CBC/PKCS7Padding"
+    val wssRegex = "^wss".toRegex()
+    val wsRegex = "^ws".toRegex()
+    val suffixString = "(-.*)?(…)?(\\(\\d+\\))?$"
+    val suffixRegex = "(-.*)?(…)?(\\(\\d+\\))?$".toRegex()
+    val groupSuffixRegex = "(…)?(\\(\\d+\\))?$".toRegex()
+    val digitalRegex = "\\(\\d+\\)\$".toRegex()
+    var weworkCorpName: String
+        get() = SPUtils.getInstance().getString("weworkCorpName", "")
+        set(value) {
+            SPUtils.getInstance().put("weworkCorpName", value)
+        }
+    var weworkCorpId: String
+        get() = SPUtils.getInstance().getString(weworkCorpName + "weworkCorpId", "")
+        set(value) {
+            SPUtils.getInstance().put(weworkCorpName + "weworkCorpId", value)
+        }
+    var weworkAgentId: String
+        get() = SPUtils.getInstance().getString(weworkCorpName + "weworkAgentId", "")
+        set(value) {
+            SPUtils.getInstance().put(weworkCorpName + "weworkAgentId", value)
+        }
+    var weworkSchema: String
+        get() = SPUtils.getInstance().getString(weworkCorpName + "weworkSchema", "")
+        set(value) {
+            SPUtils.getInstance().put(weworkCorpName + "weworkSchema", value)
+        }
+    var weworkMP: String
+        get() = SPUtils.getInstance().getString(weworkCorpName + "weworkMP", "")
+        set(value) {
+            SPUtils.getInstance().put(weworkCorpName + "weworkMP", value)
+        }
+    var encryptType: Int = SPUtils.getInstance().getInt("encryptType", 0)
+    var autoReply: Int = SPUtils.getInstance().getInt("autoReply", 1)
+    var groupStrict: Boolean
+        get() = SPUtils.getInstance().getBoolean("groupStrict", false)
+        set(value) = SPUtils.getInstance().put("groupStrict", value)
+    var friendRemarkStrict: Boolean
+        get() = SPUtils.getInstance().getBoolean("friendRemarkStrict", false)
+        set(value) = SPUtils.getInstance().put("friendRemarkStrict", value)
+    var pushImage = false
+    var autoPublishComment: Boolean
+        get() = SPUtils.getInstance().getBoolean("autoPublishComment", true)
+        set(value) = SPUtils.getInstance().put("autoPublishComment", value)
+    var groupQrCode: Boolean
+        get() = SPUtils.getInstance().getBoolean("groupQrCode", false)
+        set(value) = SPUtils.getInstance().put("groupQrCode", value)
+    var enableMediaProject = false
+    var enableSdkShare = false
+    var fullGroupName: Boolean
+        get() = SPUtils.getInstance().getBoolean("fullGroupName", true)
+        set(value) = SPUtils.getInstance().put("fullGroupName", value)
+    var customLink: Boolean
+        get() = SPUtils.getInstance().getBoolean("customLink", false)
+        set(value) = SPUtils.getInstance().put("customLink", value)
+    var customMP: Boolean
+        get() = SPUtils.getInstance().getBoolean("customMP", false)
+        set(value) = SPUtils.getInstance().put("customMP", value)
+    var robotId: String
+        get() = SPUtils.getInstance()
+            .getString("robotId", SPUtils.getInstance().getString("LISTEN_CHANNEL_ID", "1"))
+        set(value) {
+            SPUtils.getInstance().put("robotId", value)
+        }
+
+    //replyStrategy=replyAll+1   replyStrategy=0不回复 replyStrategy=1回复at replyStrategy=2回复所有
+    var replyStrategy: Int
+        get() = SPUtils.getInstance().getInt("replyStrategy", 1)
+        set(value) {
+            SPUtils.getInstance().put("replyStrategy", value)
+        }
+    var qaUrl: String
+        get() = SPUtils.getInstance().getString("qaUrl", "")
+        set(value) {
+            SPUtils.getInstance().put("qaUrl", value)
+        }
+    var openCallback: Int
+        get() = SPUtils.getInstance().getInt("openCallback", 0)
+        set(value) {
+            SPUtils.getInstance().put("openCallback", value)
+        }
+    var host: String
+        get() = SPUtils.getInstance().getString("host", DEFAULT_HOST)
+        set(value) {
+            SPUtils.getInstance().put("host", value)
+        }
+    var oldDevice: Boolean
+        get() = SPUtils.getInstance().getBoolean("oldDevice", false)
+        set(value) = SPUtils.getInstance().put("oldDevice", value)
+    var autoPassFriendRequest: Boolean
+        get() = SPUtils.getInstance().getBoolean("autoPassFriendRequest", true)
+        set(value) = SPUtils.getInstance().put("autoPassFriendRequest", value)
+    var duplicationFilter: Boolean
+        get() = SPUtils.getInstance().getBoolean("apiDuplicationFilter", true)
+        set(value) {
+            SPUtils.getInstance().put("apiDuplicationFilter", value)
+        }
+
+    fun getWsUrl() = host + robotId
+
+    fun getCheckUpdateUrl() = "${getBaseUrl()}/appUpdate/checkUpdate"
+
+    fun getMasterCheckUpdateUrl() = "https://worktool.asrtts.cn/appUpdate/checkUpdate"
+
+    fun getMyConfig() = "${getBaseUrl()}/robot/robotInfo/get?robotId=$robotId"
+
+    fun getRobotUpdateUrl() = "${getBaseUrl()}/robot/robotInfo/update?robotId=$robotId"
+
+    fun getTestUrl() = "${getBaseUrl()}/test"
+
+    fun getPushLocalFileUrl() = "${getBaseUrl()}/fileUpload/upload?robotId=$robotId"
+
+    private fun getBaseUrl() = host.replace(wssRegex, "https").replace(wsRegex, "http")
+
+}

+ 117 - 0
app/src/main/java/org/yameida/worktool/Demo.kt

@@ -0,0 +1,117 @@
+package org.yameida.worktool
+
+import com.blankj.utilcode.util.TimeUtils
+import org.yameida.worktool.service.MyLooper
+import java.util.*
+
+/**
+ * 示例
+ */
+object Demo {
+
+    fun test(flag: Boolean) {
+        if (!flag) return
+
+        MyLooper.getInstance().removeCallbacksAndMessages(null)
+
+        //打印当前视图树
+//        AccessibilityUtil.printNodeClazzTree(getRoot())
+
+    }
+
+    fun test2(name: String) {
+        val time = TimeUtils.date2String(Date(), "MMddHHmm")
+        val groupName = "$name"
+        val json = """
+            {
+                "socketType":2,
+                "list":[
+                    {
+                      "type": 210,
+                      "groupName": "$groupName",
+                      "titleList": [
+                        "杨洋测试群1"
+                      ],
+                      "fileUrl":"https://img2.baidu.com/it/u=4275140052,831704139&fm=253&fmt=auto&app=120&f=JPEG?w=399&h=399",
+                      "appId":"ww74795b61fcf19d03",
+                      "agentId":"1000009",
+                      "fileName":"1",
+                      "extraText":"aa",
+                      "maxRetryCount":1
+                    }
+                ]
+            }
+        """.trimIndent()
+        MyLooper.onMessage(null, json)
+    }
+
+    fun test3() {
+        val time = TimeUtils.date2String(Date(), "MMddHHmm")
+        val groupName = "测试群"
+        val json = """
+            {
+                "socketType":2,
+                "list":[
+                    {
+                        "type":604,
+                         "msgCheckFlag": "1",
+                         "msgKeywordList":[
+                            "aaa",
+                            "bbb",
+                            "cc"
+                         ],
+                         "miniSendFlag":"1",
+                         "miniNameList":[
+                            "和裂变"
+                         ],
+                         "urlSendFlag": 1,
+                         "urlList": [
+                         "baidu.com",
+                         "taobao.com",
+                         "aaaaa.com",
+                         "bbbbb.com"
+                        ],
+                         "qrcodeSendFlag": 1,
+                         "moreFlag":1,
+                         "photoSendFlag": 1,
+                            "chatSendFlag": 1,
+                            "voiceSendFlag": 1,
+                            "videoSendFlag": 1,
+                            "fileSendFlag": 1,
+                            "cardSendFlag": 1,
+                            "nicknameCheckFlag": 1,
+                            "nicknameKeywordList": [
+                                "昵称关键词1",
+                                "昵称关键词2",
+                                "昵称关键词3"
+                            ],
+                           "maxLengthFlag": 1,
+                            "msgMaxLength": 500,
+                           "maxLineFlag": 1,
+                            "msgMaxLine": 20
+                    }
+                ]
+            }
+        """.trimIndent()
+        MyLooper.onMessage(null, json)
+    }
+
+    fun test5(name: String) {
+        val time = TimeUtils.date2String(Date(), "MMddHHmm")
+        val groupName = "$name"
+        val json = """
+            {
+                "socketType":2,
+                "list":[
+                     {
+                      "type": 604,
+                      "groupName": [$groupName],
+                      "receivedContent":"1"
+                    }
+                ]
+            } 
+        """.trimIndent()
+        MyLooper.onMessage(null, json)
+    }
+
+}

+ 64 - 0
app/src/main/java/org/yameida/worktool/MyApplication.kt

@@ -0,0 +1,64 @@
+package org.yameida.worktool
+
+import android.app.Application
+import android.content.Intent
+import com.blankj.utilcode.util.GsonUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.Utils
+import com.efs.sdk.base.core.util.PackageUtil
+import com.google.gson.Gson
+import com.hjq.toast.ToastUtils
+import com.tencent.wework.api.IWWAPI
+import org.yameida.worktool.config.GlobalException
+import org.yameida.worktool.notification.PlayNotifyManager
+import org.yameida.worktool.utils.IWWAPIUtil
+import org.yameida.worktool.utils.LogUtilsInit
+
+class MyApplication : Application() {
+
+    companion object {
+
+        var iwwapi: IWWAPI? = null
+
+        /**
+         * 回到WorkTool首页 需要先授权显示悬浮窗
+         */
+        fun launchIntent() {
+            LogUtils.e("进入WorkTool APP~")
+            val app = Utils.getApp()
+            app.packageManager.getLaunchIntentForPackage(PackageUtil.getPackageName(app))?.apply {
+                this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                app.startActivity(this)
+            }
+        }
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+        //初始化工具类
+        Utils.init(this)
+        //初始化Log工具配置
+        LogUtilsInit.init()
+        GsonUtils.setGsonDelegate(Gson())
+        //初始化 Toast 框架
+        ToastUtils.init(this)
+        //初始化友盟统计
+//        val key = "6284a3a3d024421570f97c3c"
+//        val channel = "main_channel"
+//        UMConfigure.preInit(this, key, channel)
+//        //判断是否同意隐私协议,uminit为1时为已经同意,直接初始化umsdk
+//        if (SPUtils.getInstance().getString("uminit", "1") == "1") {
+//            UMConfigure.init(this, key, channel, UMConfigure.DEVICE_TYPE_PHONE, "")
+//        }
+//        TalkingDataSDK.init(this, "80E9C84E39904DAFB28562910FF7C86C", getString(R.string.app_name) + "_master", Constant.robotId)
+        //初始化企业微信sdk
+        IWWAPIUtil.init(this)
+        //初始化自动更新
+//        UpdateAppUtils.init(this)
+        //初始化前台服务
+        PlayNotifyManager.show()
+        //设置全局异常捕获重启
+        Thread.setDefaultUncaughtExceptionHandler(GlobalException.getInstance())
+    }
+
+}

+ 65 - 0
app/src/main/java/org/yameida/worktool/activity/AccessibilityGuideActivity.kt

@@ -0,0 +1,65 @@
+package org.yameida.worktool.activity
+
+import android.content.Intent
+import android.os.Bundle
+import android.provider.Settings
+import android.view.View
+import android.view.animation.AlphaAnimation
+import android.view.animation.Animation
+import androidx.appcompat.app.AppCompatActivity
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.SPUtils
+import kotlinx.android.synthetic.main.activity_accessibility_guide.*
+import org.yameida.worktool.R
+import org.yameida.worktool.utils.PermissionHelper
+
+/**
+ * Created by gallon on 2019/7/20.
+ * 提示开启无障碍服务权限
+ */
+class AccessibilityGuideActivity: AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_accessibility_guide)
+        window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE or
+                View.SYSTEM_UI_FLAG_FULLSCREEN or
+                View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+                View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+
+        tv_float_allow.setOnClickListener {
+            try {
+                if (!PermissionHelper.isAccessibilitySettingOn()) {
+                    val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
+                    startActivity(intent)
+                }
+            } catch (t: Throwable) {}
+        }
+        tv_float_reject.setOnClickListener {
+            finish()
+        }
+        cb_guide_not.isChecked = SPUtils.getInstance().getBoolean("not_show_accessibility_guide", false)
+        cb_guide_not.setOnCheckedChangeListener { buttonView, isChecked ->
+            SPUtils.getInstance().put("not_show_accessibility_guide", isChecked)
+        }
+
+        val alphaAnimation = AlphaAnimation(0.2F, 1F).apply {
+            duration = 800
+            repeatCount = Animation.INFINITE
+            repeatMode = Animation.REVERSE
+        }
+        iv_over_finger.startAnimation(alphaAnimation)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        val accessibilitySettingOn = PermissionHelper.isAccessibilitySettingOn()
+        LogUtils.d("PermissionHelper.isAccessibilitySettingOn: $accessibilitySettingOn")
+        if (accessibilitySettingOn) {
+            finish()
+        }
+    }
+
+}

+ 26 - 0
app/src/main/java/org/yameida/worktool/activity/BrowserActivity.kt

@@ -0,0 +1,26 @@
+package org.yameida.worktool.activity
+
+import android.os.Bundle
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+import kotlinx.android.synthetic.main.activity_browser.*
+import org.yameida.worktool.R
+
+/**
+ * 浏览器页
+ */
+class BrowserActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+        setContentView(R.layout.activity_browser)
+
+        initView()
+    }
+
+    private fun initView() {
+        qmwv.loadUrl("https://wt.asrtts.cn")
+    }
+}

+ 88 - 0
app/src/main/java/org/yameida/worktool/activity/FloatViewGuideActivity.kt

@@ -0,0 +1,88 @@
+package org.yameida.worktool.activity
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.provider.Settings
+import android.view.View
+import android.view.animation.AlphaAnimation
+import android.view.animation.Animation
+import androidx.appcompat.app.AppCompatActivity
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.SPUtils
+import com.blankj.utilcode.util.ToastUtils
+import com.blankj.utilcode.util.Utils
+import kotlinx.android.synthetic.main.activity_float_guide.*
+import org.yameida.worktool.R
+import org.yameida.worktool.utils.FloatWindowHelper
+import org.yameida.worktool.utils.FlowPermissionHelper
+import org.yameida.worktool.utils.PermissionPageManagement
+
+/**
+ * Created by gallon on 2019/7/20.
+ * 提示开启悬浮窗权限
+ */
+class FloatViewGuideActivity: AppCompatActivity() {
+
+    private var goToSetting = false
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_float_guide)
+        window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE or
+                View.SYSTEM_UI_FLAG_FULLSCREEN or
+                View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+                View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+
+        tv_float_allow.setOnClickListener {
+            try {
+                if (!Settings.canDrawOverlays(Utils.getApp())) {
+                    val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
+                        data = Uri.parse("package:$packageName")
+                    }
+                    startActivity(intent)
+                }
+            } catch (t: Throwable) {}
+        }
+        tv_float_reject.setOnClickListener {
+            finish()
+        }
+        cb_guide_not.isChecked = SPUtils.getInstance().getBoolean("not_show_float_guide", false)
+        cb_guide_not.setOnCheckedChangeListener { buttonView, isChecked ->
+            SPUtils.getInstance().put("not_show_float_guide", isChecked)
+        }
+
+        val alphaAnimation = AlphaAnimation(0.2F, 1F).apply {
+            duration = 800
+            repeatCount = Animation.INFINITE
+            repeatMode = Animation.REVERSE
+        }
+        iv_over_finger.startAnimation(alphaAnimation)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        val canDrawOverlays = Settings.canDrawOverlays(Utils.getApp())
+        LogUtils.d("Settings.canDrawOverlays: $canDrawOverlays")
+        if (canDrawOverlays) {
+            if (goToSetting) {
+                if (FlowPermissionHelper.canBackgroundStart(Utils.getApp())) {
+                    FloatWindowHelper.showWindow()
+                }
+                finish()
+            } else if (!FlowPermissionHelper.canBackgroundStart(Utils.getApp())) {
+                ToastUtils.showLong("请同时打开后台弹出界面权限~")
+                PermissionPageManagement.goToSetting(this)
+                goToSetting = true
+            } else {
+                if (FlowPermissionHelper.canBackgroundStart(Utils.getApp())) {
+                    FloatWindowHelper.showWindow()
+                }
+                finish()
+            }
+        }
+    }
+
+}

+ 134 - 0
app/src/main/java/org/yameida/worktool/activity/GetScreenShotActivity.kt

@@ -0,0 +1,134 @@
+package org.yameida.worktool.activity
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.PixelFormat
+import android.hardware.display.DisplayManager
+import android.media.Image
+import android.media.ImageReader
+import android.media.projection.MediaProjectionManager
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+import com.blankj.utilcode.constant.PermissionConstants
+import com.blankj.utilcode.util.*
+import org.yameida.worktool.service.PlayNotifyService
+import org.yameida.worktool.service.fastStartActivity
+import org.yameida.worktool.utils.capture.MediaProjectionHolder
+import org.yameida.worktool.utils.startServiceSafe
+
+
+/**
+ * Created by Gallon on 2019/7/30.
+ */
+class GetScreenShotActivity : AppCompatActivity() {
+    private var mediaProjectionManager: MediaProjectionManager? = null
+    private var hideFloatWindow = false
+    private val handler = Handler(Looper.getMainLooper())
+
+    companion object {
+        val HIDE_FLOAT_WINDOW = "hideFloatWindow"
+
+        fun startCapture(): Bitmap? {
+            if (MediaProjectionHolder.mMediaProjection == null) {
+                LogUtils.e("截图失败 mediaProjection未初始化")
+                fastStartActivity(Utils.getApp(), GetScreenShotActivity::class.java)
+                return null
+            }
+            val imageReader = ImageReader.newInstance(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight(), PixelFormat.RGBA_8888, 1)
+            val virtualDisplay = MediaProjectionHolder.mMediaProjection?.createVirtualDisplay("ScreenShout",
+                    ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight(), ScreenUtils.getScreenDensityDpi(),
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+                    imageReader.surface, null, null)
+
+            var image: Image? = null
+            var tryCount = 0
+            while (tryCount < 10 && image == null) {
+                SystemClock.sleep(250)
+                image = imageReader.acquireNextImage()
+            }
+            if (image == null) {
+                LogUtils.i("GetScreenShotActivity", "image is null.")
+                return null
+            }
+            val width = image.width
+            val height = image.height
+            val planes = image.planes
+            val buffer = planes[0].buffer
+            val pixelStride = planes[0].pixelStride
+            val rowStride = planes[0].rowStride
+            val rowPadding = rowStride - pixelStride * width
+            var bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888)
+            bitmap.copyPixelsFromBuffer(buffer)
+            bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height)
+            image.close()
+            imageReader.close()
+            virtualDisplay?.release()
+            return bitmap
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        LogUtils.i("onCreate")
+        window.statusBarColor = Color.TRANSPARENT
+        mediaProjectionManager = Utils.getApp().getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
+        hideFloatWindow = intent.getBooleanExtra(HIDE_FLOAT_WINDOW, false)
+
+        PermissionUtils.permission(PermissionConstants.STORAGE)
+                .callback(object : PermissionUtils.SimpleCallback {
+                    override fun onGranted() {
+                        LogUtils.d("start record")
+                        mediaProjectionManager?.apply {
+                            val intent = this.createScreenCaptureIntent()
+                            if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
+                                startActivityForResult(intent, 0)
+                            } else {
+                                ToastUtils.showShort("抱歉,你的手机暂不支持录屏")
+                            }
+                        }
+                    }
+
+                    override fun onDenied() {
+                        ToastUtils.showShort("请允许申请的权限,否则无法录屏")
+                        finish()
+                    }
+                })
+                .request()
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        LogUtils.i("onActivityResult")
+
+        if (resultCode == Activity.RESULT_OK && data != null) {
+            LogUtils.e("mediaProjectionManager: $mediaProjectionManager")
+            LogUtils.e("resultCode: $resultCode")
+            LogUtils.e("data: $data")
+            try {
+                val mWindowManager = getSystemService(WINDOW_SERVICE) as WindowManager
+                val metrics = DisplayMetrics()
+                mWindowManager.defaultDisplay.getMetrics(metrics)
+            } catch (e: Exception) {
+                LogUtils.e("MediaProjection error")
+            }
+            sendBroadcast(Intent())
+            val service = Intent(this, PlayNotifyService::class.java)
+            service.putExtra("setMediaProject", true)
+            service.putExtra("code", resultCode)
+            service.putExtra("data", data)
+            startServiceSafe(service)
+        }
+        finish()
+    }
+
+
+}

+ 364 - 0
app/src/main/java/org/yameida/worktool/activity/ListenActivity.kt

@@ -0,0 +1,364 @@
+package org.yameida.worktool.activity
+
+import android.content.*
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.provider.Settings
+import android.text.InputType
+import android.view.WindowManager
+import android.widget.CompoundButton
+import androidx.appcompat.app.AppCompatActivity
+import com.blankj.utilcode.constant.PermissionConstants
+import com.blankj.utilcode.util.*
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.lzy.okgo.OkGo
+import com.qmuiteam.qmui.widget.dialog.QMUIDialog
+import kotlinx.android.synthetic.main.activity_listen.*
+import org.yameida.worktool.Constant
+import org.yameida.worktool.R
+import org.yameida.worktool.service.PlayNotifyService
+import org.yameida.worktool.service.WeworkController
+import org.yameida.worktool.service.fastStartActivity
+import org.yameida.worktool.utils.*
+import org.yameida.worktool.utils.capture.MediaProjectionHolder
+import org.yameida.worktool.utils.envcheck.CheckHook
+import org.yameida.worktool.utils.envcheck.CheckRoot
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
+
+
+class ListenActivity : AppCompatActivity() {
+
+    var riskRetry: Int = 0
+
+    val handler = Handler()
+
+    companion object {
+        /**
+         * @param type 0=游客登录
+         */
+        fun enterActivity(context: Context, type: Int) {
+            LogUtils.d("ListenActivity.enterActivity type: $type")
+            context.startActivity(Intent(context, ListenActivity::class.java).apply {
+                this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                putExtra("type", type)
+            })
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+        setContentView(R.layout.activity_listen)
+
+        initView()
+        initAccessibility()
+        initOverlays()
+        initData()
+        initNotification()
+        PermissionUtils.permission(PermissionConstants.STORAGE).request()
+        registerReceiver(openWsReceiver, IntentFilter(Constant.WEWORK_NOTIFY))
+
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        unregisterReceiver(openWsReceiver)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        sw_overlay.isChecked =
+            Settings.canDrawOverlays(Utils.getApp()) && FlowPermissionHelper.canBackgroundStart(
+                Utils.getApp()
+            )
+        sw_accessibility.isChecked = PermissionHelper.isAccessibilitySettingOn()
+
+        if (needToWork) {
+            needToWork = false
+            goToWork()
+        }
+    }
+
+    private fun initView() {
+        iv_settings.setOnClickListener {
+            SettingsActivity.enterActivity(this)
+        }
+        et_channel.setText(Constant.robotId)
+        bt_save.setOnClickListener {
+            val channel = et_channel.text.toString().trim()
+            Constant.robotId = channel
+            ToastUtils.showLong("保存成功")
+//            sendBroadcast(Intent(Constant.WEWORK_NOTIFY).apply {
+//                putExtra("type", "modify_channel")
+//            })
+//            HttpUtil.getMyConfig(toast = false)
+//            MobclickAgent.onProfileSignIn(channel)
+//            handler.postDelayed({
+//                Demo.test3()
+//            },8000)
+//            test()
+        }
+        tv_host.text = Constant.host
+        tv_host.setOnClickListener {
+            showSelectHostDialog()
+        }
+        tv_host.setOnLongClickListener {
+            showInputHostDialog()
+            true
+        }
+        val version =
+            "${AppUtils.getAppVersionName()}     Android ${DeviceUtils.getSDKVersionName()} ${DeviceUtils.getManufacturer()} ${DeviceUtils.getModel()}"
+        val deviceRooted = CheckRoot.isDeviceRooted()
+        val hook = CheckHook.isHook(applicationContext)
+        if (hook) {
+            tv_version.text = "当前设备存在侵入代码,请勿在本设备使用本程序!!!"
+        } else if (deviceRooted) {
+            tv_version.text = "$version\n本设备已Root,存在一定风险!"
+        } else {
+            tv_version.text = version
+        }
+        val workVersionName = AppUtils.getAppInfo(Constant.PACKAGE_NAMES)?.versionName
+        when (workVersionName) {
+            null -> {
+                LogUtils.e("系统检测到您尚未安装企业微信,请先安装企业微信")
+                tv_work_version.text = "检测到您尚未安装企业微信,请先安装登录!"
+            }
+            in Constant.AVAILABLE_VERSION -> {
+                LogUtils.i("当前企业微信版本已适配: $workVersionName")
+                val tip = "$workVersionName   已适配,可放心使用~"
+                tv_work_version.text = tip
+                Constant.version = Constant.AVAILABLE_VERSION_MAP[workVersionName] ?: Int.MAX_VALUE
+            }
+            else -> {
+                LogUtils.e("当前企业微信版本未兼容: $workVersionName")
+                val tip = "$workVersionName   可能存在部分兼容性问题!"
+                tv_work_version.text = tip
+            }
+        }
+        LogUtils.i("Constant.version: ${Constant.version}")
+        SPUtils.getInstance().put("appVersion", version)
+        SPUtils.getInstance().put("workVersion", workVersionName)
+        SPUtils.getInstance().put("deviceRooted", deviceRooted)
+        SPUtils.getInstance().put("hook", hook)
+    }
+
+    private fun initAccessibility() {
+        sw_accessibility.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_accessibility onCheckedChanged: $isChecked")
+            if (isChecked) {
+                if (Constant.robotId.isBlank()) {
+                    sw_accessibility.isChecked = false
+                    ToastUtils.showLong("请先填写并保存链接号~")
+                } else if (!PermissionHelper.isAccessibilitySettingOn()) {
+                    if (SPUtils.getInstance().getBoolean("risk", false)) {
+                        if (riskRetry > 10) {
+                            ToastUtils.showLong("再点${20 - riskRetry}次 允许本次运行")
+                        } else {
+                            ToastUtils.showLong("环境监测异常,请勿使用本应用!")
+                        }
+                        if (++riskRetry > 20) {
+                            SPUtils.getInstance().put("risk", false)
+                            startActivity(Intent(this, AccessibilityGuideActivity::class.java))
+                            ToastUtils.showLong("风险提示:临时允许本次运行")
+                        }
+                        sw_accessibility.isChecked = false
+                    } else {
+                        startActivity(Intent(this, AccessibilityGuideActivity::class.java))
+                    }
+                }
+            } else {
+                if (PermissionHelper.isAccessibilitySettingOn()) {
+                    if (FlowPermissionHelper.isBlueCloud()) {
+                        sw_accessibility.isChecked = true
+                        val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
+                        startActivity(intent)
+                    } else {
+                        WeworkController.weworkService.disableSelf()
+                    }
+                }
+            }
+        })
+    }
+
+    private fun initOverlays() {
+        sw_overlay.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_overlay onCheckedChanged: $isChecked")
+            if (isChecked) {
+                startActivity(Intent(this, FloatViewGuideActivity::class.java))
+            } else {
+                if (Settings.canDrawOverlays(Utils.getApp()) && FlowPermissionHelper.canBackgroundStart(
+                        Utils.getApp()
+                    )
+                ) {
+                    sw_overlay.isChecked = true
+                    PermissionPageManagement.goToSetting(this)
+                }
+            }
+        })
+        if (Settings.canDrawOverlays(Utils.getApp()) && FlowPermissionHelper.canBackgroundStart(
+                Utils.getApp()
+            )
+        ) {
+            FloatWindowHelper.showWindow()
+        }
+    }
+
+    private fun initData() {
+//        HttpUtil.checkUpdate()
+        HttpUtil.getMyConfig(toast = false)
+        CacheUtil.autoDelete()
+    }
+
+    private fun initNotification() {
+        if (!Constant.enableMediaProject) {
+            return
+        }
+        application.bindService(
+            Intent(application, PlayNotifyService::class.java),
+            object : ServiceConnection {
+                override fun onServiceDisconnected(name: ComponentName) {
+                    LogUtils.d("onServiceDisconnected")
+                }
+
+                override fun onServiceConnected(name: ComponentName, iBinder: IBinder) {
+                    LogUtils.d("onServiceConnected")
+                }
+            },
+            Context.BIND_AUTO_CREATE
+        )
+        //开启屏幕录制权限
+        if (MediaProjectionHolder.mMediaProjection == null) {
+            bt_save.postDelayed({
+                fastStartActivity(this, GetScreenShotActivity::class.java)
+            }, 1000)
+        }
+    }
+
+    private fun showSelectHostDialog() {
+        val hostList = SPUtils.getInstance().getStringSet("host_list", mutableSetOf(Constant.host))
+        if (hostList.isNotEmpty()) {
+            val hostArray = hostList.toTypedArray()
+            QMUIDialog.CheckableDialogBuilder(this)
+                .setTitle(getString(R.string.host_list))
+                .addItems(hostArray) { dialog, which ->
+                    Constant.host = hostArray[which]
+                    tv_host.text = hostArray[which]
+                    HostTestHelper.testWs()
+                    dialog.dismiss()
+                }
+                .setCheckedIndex(hostList.indexOf(Constant.host))
+                .create(R.style.QMUI_Dialog)
+                .show()
+        }
+    }
+
+    private fun showInputHostDialog() {
+        ToastUtils.showLong("请输入专线网络")
+        val builder = QMUIDialog.EditTextDialogBuilder(this)
+        builder.setTitle(getString(R.string.tip))
+            .setPlaceholder(getString(R.string.input_new_host))
+            .setDefaultText(tv_host.text)
+            .setInputType(InputType.TYPE_CLASS_TEXT)
+            .addAction(getString(R.string.delete)) { dialog, index ->
+                val hostList =
+                    SPUtils.getInstance().getStringSet("host_list", mutableSetOf(Constant.host))
+                if (hostList.size > 1) {
+                    hostList.remove(Constant.host)
+                    Constant.host = hostList.elementAt(0)
+                    tv_host.text = Constant.host
+                    HostTestHelper.testWs()
+                    SPUtils.getInstance().put("host_list", hostList)
+                    dialog.dismiss()
+                } else {
+                    ToastUtils.showLong("至少保留一个host!")
+                }
+            }
+            .addAction(getString(R.string.cancel)) { dialog, index -> dialog.dismiss() }
+            .addAction(getString(R.string.add)) { dialog, index ->
+                val text = builder.editText.text
+                if (text != null && text.isNotEmpty()) {
+                    if (text.matches("ws{1,2}://[^/]+.*".toRegex())) {
+                        val hostList = SPUtils.getInstance()
+                            .getStringSet("host_list", mutableSetOf(Constant.host))
+                        hostList.add(text.toString())
+                        SPUtils.getInstance().put("host_list", hostList)
+                        Constant.host = text.toString()
+                        tv_host.text = text
+                        HostTestHelper.testWs()
+                        dialog.dismiss()
+                    } else {
+                        ToastUtils.showLong("格式异常!")
+                    }
+                } else {
+                    ToastUtils.showLong("请勿为空!")
+                }
+            }
+            .create(R.style.QMUI_Dialog).show()
+    }
+
+    private var needToWork = false
+
+    private fun goToWork() {
+        val positiveButton =
+            MaterialAlertDialogBuilder(this, R.style.Theme_MaterialComponents_DayNight_Dialog)
+                .setTitle("设置成功")
+                .setMessage("请勿人工操作手机     \n5秒后自动跳转")
+                .setNegativeButton("", null)
+                .setPositiveButton("", null)
+        val show = positiveButton.show()
+        bt_save.postDelayed({ show.dismiss() }, 5000)
+        bt_save.postDelayed({
+            packageManager.getLaunchIntentForPackage(Constant.PACKAGE_NAMES)?.apply {
+                this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                startActivity(this)
+            }
+        }, 5000)
+    }
+
+    private val openWsReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (intent.getStringExtra("type") == "openWs") {
+                needToWork = intent.getBooleanExtra("switch", false)
+            }
+        }
+    }
+
+    fun test(){
+        val imageUrl="https://assistant.flowbb.top/assistant/4af01bb1e19abaf0b4d6bcb4a620ba26.jpg/4af01bb1e19abaf0b4d6bcb4a620ba26.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20241219%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20241219T063441Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=2e45427335a9ec2573b1125d23297c555d08dd146b090bf254015ff0b149f70f"
+        Thread {
+            val execute = OkGo.get<File>(imageUrl).execute()
+            LogUtils.i("下载完成 $imageUrl")
+            val body = execute.body()
+            if (body != null) {
+                val df = SimpleDateFormat("yyyy-MM-dd")
+                val filePath = "${
+                    Utils.getApp().getExternalFilesDir("mp_image_cache")
+                }/${df.format(Date())}/${imageUrl.split("/").lastOrNull()}"
+                val newFile = File(filePath)
+                if (FileUtils.isFile(newFile)) {
+                    FileUtils.delete(newFile)
+                }
+                val create = FileUtils.createFileByDeleteOldFile(newFile)
+                if (create && newFile.canWrite()) {
+                    newFile.writeBytes(body.bytes())
+                    LogUtils.i("文件存储本地成功 $filePath")
+                    val bitmap = ImageUtils.bytes2Bitmap(File(filePath).readBytes())
+                    val stream = ByteArrayOutputStream()
+                    bitmap.compress(Bitmap.CompressFormat.JPEG, 75, stream)
+                } else {
+                    LogUtils.e("文件存储本地失败 $filePath")
+                }
+            } else {
+                LogUtils.e("文件下载失败")
+            }
+        }.start()
+
+    }
+
+}

+ 37 - 0
app/src/main/java/org/yameida/worktool/activity/LoginActivity.kt

@@ -0,0 +1,37 @@
+package org.yameida.worktool.activity
+
+import android.os.Bundle
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+import kotlinx.android.synthetic.main.activity_login.*
+import org.yameida.worktool.R
+import java.util.*
+
+/**
+ * 登录页
+ */
+class LoginActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+        setContentView(R.layout.activity_login)
+
+        initView()
+    }
+
+    private fun initView() {
+        val hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
+        if (hour in 12..17) {
+            textView.text = "Afternoon"
+        } else if (hour in 18..24 || hour in 0..6) {
+            textView.text = "Night"
+            imageView.setImageResource(R.drawable.good_night_img)
+        }
+        tv_visitor_login.setOnClickListener {
+            ListenActivity.enterActivity(this, 0)
+            finish()
+        }
+    }
+}

+ 235 - 0
app/src/main/java/org/yameida/worktool/activity/SettingsActivity.kt

@@ -0,0 +1,235 @@
+package org.yameida.worktool.activity
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.provider.Settings
+import android.view.WindowManager
+import android.widget.CompoundButton
+import androidx.appcompat.app.AppCompatActivity
+import com.blankj.utilcode.util.*
+import com.lzy.okgo.OkGo
+import com.lzy.okgo.callback.StringCallback
+import com.lzy.okgo.model.Response
+import com.qmuiteam.qmui.widget.dialog.QMUIDialog
+import kotlinx.android.synthetic.main.activity_settings.*
+import okhttp3.MediaType
+import okhttp3.RequestBody
+import org.json.JSONObject
+import org.yameida.worktool.Constant
+import org.yameida.worktool.R
+import org.yameida.worktool.service.WeworkController
+import org.yameida.worktool.utils.*
+import java.io.File
+
+
+/**
+ * 设置页
+ */
+class SettingsActivity : AppCompatActivity() {
+
+    companion object {
+        fun enterActivity(context: Context) {
+            LogUtils.d("SettingsActivity.enterActivity")
+            context.startActivity(Intent(context, SettingsActivity::class.java).apply {
+                this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            })
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+        setContentView(R.layout.activity_settings)
+
+        initView()
+        initData()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        freshOpenFlow()
+        freshOpenMain()
+    }
+
+    private fun initView() {
+        iv_back_left.setOnClickListener { finish() }
+        sw_encrypt.isChecked = Constant.encryptType == 1
+        sw_encrypt.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_encrypt onCheckedChanged: $isChecked")
+            Constant.encryptType = if (isChecked) 1 else 0
+            SPUtils.getInstance().put("encryptType", Constant.encryptType)
+        })
+        sw_receive.isChecked = Constant.autoReply == 1
+        sw_receive.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_receive onCheckedChanged: $isChecked")
+            Constant.autoReply = if (isChecked) 1 else 0
+            SPUtils.getInstance().put("autoReply", Constant.autoReply)
+        })
+        rl_reply_strategy.setOnClickListener { showReplyStrategyDialog() }
+        rl_log.setOnClickListener { showLogDialog() }
+        rl_update.setOnClickListener { showUpdateDialog() }
+        rl_donate.setOnClickListener { showDonateDialog() }
+        rl_share.setOnClickListener { showShareDialog() }
+        rl_advance.setOnClickListener { SettingsAdvanceActivity.enterActivity(this) }
+        freshOpenFlow()
+        bt_open_flow.setOnClickListener {
+            freshOpenFlow()
+            if (Settings.canDrawOverlays(Utils.getApp())) {
+                if (!FlowPermissionHelper.canBackgroundStart(Utils.getApp())) {
+                    ToastUtils.showLong("请同时打开后台弹出界面权限~")
+                    PermissionPageManagement.goToSetting(this)
+                } else {
+                    FloatWindowHelper.showWindow()
+                }
+            } else {
+                startActivity(Intent(this, FloatViewGuideActivity::class.java))
+            }
+        }
+        freshOpenMain()
+        bt_open_main.setOnClickListener {
+            freshOpenMain()
+            if (PermissionHelper.isAccessibilitySettingOn()) {
+                if (FlowPermissionHelper.isBlueCloud()) {
+                    val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
+                    startActivity(intent)
+                } else {
+                    WeworkController.weworkService.disableSelf()
+                    freshOpenMain()
+                }
+            } else {
+                if (Constant.robotId.isBlank()) {
+                    ToastUtils.showLong("请先填写并保存链接号~")
+                } else if (!PermissionHelper.isAccessibilitySettingOn()) {
+                    startActivity(Intent(this, AccessibilityGuideActivity::class.java))
+                }
+            }
+        }
+    }
+
+    private fun initData() {
+        HttpUtil.getMyConfig()
+    }
+
+    private fun showReplyStrategyDialog() {
+        val strategyArray = arrayOf("只读消息不回调", "仅私聊和群聊@机器人回调", "私聊群聊全部回调")
+        QMUIDialog.CheckableDialogBuilder(this)
+            .setTitle("回复策略")
+            .addItems(strategyArray) { dialog, which ->
+                dialog.dismiss()
+                updateRobotReplyStrategy(which)
+            }
+            .setCheckedIndex(Constant.replyStrategy)
+            .create(R.style.QMUI_Dialog)
+            .show()
+    }
+
+    private fun showLogDialog() {
+        val logDir = Utils.getApp().getExternalFilesDir("log")
+        if (logDir != null && logDir.exists()) {
+            logDir.listFiles()?.forEach {
+                if (it.name.endsWith(".snapshot")) it.delete()
+            }
+            val listFiles = logDir.listFiles()
+            QMUIDialog.CheckableDialogBuilder(this)
+                .setTitle("日志文件分享")
+                .addItems(listFiles.map { it.name.replace("_" + AppUtils.getAppPackageName(), "") }.toTypedArray()) { dialog, which ->
+                    dialog.dismiss()
+                    ToastUtils.showLong(listFiles[which].name)
+                    val currentLogFilePath = listFiles[which].absolutePath
+                    FileUtils.copy(currentLogFilePath, "$currentLogFilePath.snapshot")
+                    ShareUtil.share("*/*", File("$currentLogFilePath.snapshot"), auto = false)
+                }
+                .create(R.style.QMUI_Dialog)
+                .show()
+        } else {
+            ToastUtils.showLong("日志文件夹为空~")
+        }
+    }
+
+    private fun showUpdateDialog() {
+        if (Constant.getMasterCheckUpdateUrl() == Constant.getCheckUpdateUrl()) {
+            HttpUtil.checkUpdate()
+        } else {
+            QMUIDialog.CheckableDialogBuilder(this)
+                .setTitle("检查新版本")
+                .addItems(arrayOf("检查当前Host新版本", "检查${getString(R.string.app_name)}官方新版本")) { dialog, which ->
+                    dialog.dismiss()
+                    if (which == 0) {
+                        HttpUtil.checkUpdate()
+                    } else {
+                        HttpUtil.checkUpdate(Constant.getMasterCheckUpdateUrl())
+                    }
+                }
+                .create(R.style.QMUI_Dialog)
+                .show()
+        }
+    }
+
+    private fun showDonateDialog() {
+        DonateUtil.zfbDonate(this)
+    }
+
+    private fun showShareDialog() {
+        startActivity(Intent.createChooser(Intent().apply {
+            action = Intent.ACTION_SEND
+            type = ShareUtil.TEXT
+            putExtra(Intent.EXTRA_TEXT, "我发现一个非常好用的企业微信机器人程序,文档地址: https://worktool.apifox.cn/ APP下载地址是: https://cdn.asrtts.cn/uploads/worktool/apk/worktool-latest.apk")
+        }, "分享"))
+    }
+
+    private fun freshOpenFlow() {
+        if (Settings.canDrawOverlays(Utils.getApp())) {
+            if (FlowPermissionHelper.canBackgroundStart(Utils.getApp())) {
+                bt_open_flow.setBackgroundResource(R.drawable.comment_gray_btn)
+                bt_open_flow.text = "悬浮窗权限已开启"
+            } else {
+                bt_open_flow.setBackgroundResource(R.drawable.comment_red_btn)
+                bt_open_flow.text = "开启后台弹出界面"
+            }
+        } else {
+            bt_open_flow.setBackgroundResource(R.drawable.comment_red_btn)
+            bt_open_flow.text = "开启悬浮窗权限"
+        }
+    }
+
+    private fun freshOpenMain() {
+        if (PermissionHelper.isAccessibilitySettingOn()) {
+            bt_open_main.setBackgroundResource(R.drawable.comment_gray_btn)
+            bt_open_main.text = "主功能已开启"
+        } else {
+            bt_open_main.setBackgroundResource(R.drawable.comment_red_btn)
+            bt_open_main.text = "开启主功能"
+        }
+    }
+
+    private fun updateRobotReplyStrategy(type: Int) {
+        try {
+            val json = hashMapOf<String, Any>()
+            json["robotId"] = Constant.robotId
+            json["replyAll"] = type - 1
+            val requestBody = RequestBody.create(
+                MediaType.parse("application/json;charset=UTF-8"),
+                GsonUtils.toJson(json)
+            )
+            val call = object : StringCallback() {
+                override fun onSuccess(response: Response<String>?) {
+                    if (response != null && JSONObject(response.body()).getInt("code") == 200) {
+                        Constant.replyStrategy = type
+                        ToastUtils.showLong("更新成功")
+                    } else {
+                        onError(response)
+                    }
+                }
+
+                override fun onError(response: Response<String>?) {
+                    ToastUtils.showLong("更新失败,请稍后再试~")
+                }
+            }
+            OkGo.post<String>(Constant.getRobotUpdateUrl()).upRequestBody(requestBody).execute(call)
+        } catch (e: Exception) {
+            throw RuntimeException(e)
+        }
+    }
+}

+ 275 - 0
app/src/main/java/org/yameida/worktool/activity/SettingsAdvanceActivity.kt

@@ -0,0 +1,275 @@
+package org.yameida.worktool.activity
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.text.InputType
+import android.view.View
+import android.view.WindowManager
+import android.widget.CompoundButton
+import androidx.appcompat.app.AppCompatActivity
+import com.blankj.utilcode.util.*
+import com.lzy.okgo.OkGo
+import com.lzy.okgo.callback.StringCallback
+import com.lzy.okgo.model.Response
+import com.qmuiteam.qmui.widget.dialog.QMUIDialog
+import kotlinx.android.synthetic.main.activity_settings_advance.*
+import okhttp3.MediaType
+import okhttp3.RequestBody
+import org.json.JSONObject
+import org.yameida.worktool.Constant
+import org.yameida.worktool.R
+import org.yameida.worktool.utils.HttpUtil
+import org.yameida.worktool.utils.IWWAPIUtil
+import java.util.*
+
+
+/**
+ * 高级选项页
+ */
+class SettingsAdvanceActivity : AppCompatActivity() {
+
+    companion object {
+        fun enterActivity(context: Context) {
+            LogUtils.d("SettingsAdvanceActivity.enterActivity")
+            context.startActivity(Intent(context, SettingsAdvanceActivity::class.java).apply {
+                this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            })
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+        setContentView(R.layout.activity_settings_advance)
+
+        initView()
+        initData()
+    }
+
+    private fun initView() {
+        iv_back_left.setOnClickListener { finish() }
+        sw_full_name.isChecked = Constant.fullGroupName
+        sw_full_name.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_full_name onCheckedChanged: $isChecked")
+            Constant.fullGroupName = isChecked
+        })
+        sw_qr_code.isChecked = Constant.groupQrCode
+        sw_qr_code.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_qr_code onCheckedChanged: $isChecked")
+            Constant.groupQrCode = isChecked
+        })
+        sw_auto_publish.isChecked = Constant.autoPublishComment
+        sw_auto_publish.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_auto_publish onCheckedChanged: $isChecked")
+            Constant.autoPublishComment = isChecked
+        })
+        sw_old_device.isChecked = Constant.oldDevice
+        sw_old_device.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_old_device onCheckedChanged: $isChecked")
+            Constant.oldDevice = isChecked
+            updateOldDeviceConfig()
+        })
+        sw_auto_pass_friend_request.isChecked = Constant.autoPassFriendRequest
+        sw_auto_pass_friend_request.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+            LogUtils.i("sw_auto_pass_friend_request onCheckedChanged: $isChecked")
+            Constant.autoPassFriendRequest = isChecked
+        })
+        ll_corp_param.visibility = if (Constant.customLink) View.VISIBLE else View.GONE
+        rl_username.visibility = if (Constant.customMP) View.VISIBLE else View.GONE
+        rl_qa_url.setOnClickListener { showQaUrlDialog() }
+        rl_corp_name.setOnClickListener { showCorpNameDialog() }
+        rl_corp.setOnClickListener { showCorpIdDialog() }
+        rl_agent.setOnClickListener { showAgentIdDialog() }
+        rl_schema.setOnClickListener { showSchemaDialog() }
+        rl_username.setOnClickListener { showUserNameDialog() }
+        rl_signature.setOnClickListener { showSignatureDialog() }
+    }
+
+    private fun initData() {
+        HttpUtil.getMyConfig()
+    }
+
+    private fun showQaUrlDialog() {
+        val builder = QMUIDialog.EditTextDialogBuilder(this)
+        builder.setTitle("消息回调地址")
+            .setPlaceholder("请输入回调接口地址")
+            .setDefaultText(Constant.qaUrl)
+            .setInputType(InputType.TYPE_CLASS_TEXT)
+            .addAction(getString(R.string.delete)) { dialog, index ->
+                dialog.dismiss()
+                updateRobotQaUrl("")
+            }
+            .addAction(getString(R.string.cancel)) { dialog, index -> dialog.dismiss() }
+            .addAction(getString(R.string.add)) { dialog, index ->
+                val text = builder.editText.text
+                if (text != null) {
+                    if (text.matches("https?://[^/]+.*".toRegex())) {
+                        dialog.dismiss()
+                        updateRobotQaUrl(text.toString().trim())
+                    } else {
+                        ToastUtils.showLong("格式异常!")
+                    }
+                } else {
+                    ToastUtils.showLong("请勿为空!")
+                }
+            }
+            .create(R.style.QMUI_Dialog).show()
+    }
+
+    private fun updateRobotQaUrl(callbackUrl: String) {
+        try {
+            val json = hashMapOf<String, Any>()
+            json["robotId"] = Constant.robotId
+            if (callbackUrl.isEmpty()) {
+                json["openCallback"] = 0
+            } else {
+                json["openCallback"] = 1
+                json["callbackUrl"] = callbackUrl
+            }
+            val requestBody = RequestBody.create(
+                MediaType.parse("application/json;charset=UTF-8"),
+                GsonUtils.toJson(json)
+            )
+            val call = object : StringCallback() {
+                override fun onSuccess(response: Response<String>?) {
+                    if (response != null && JSONObject(response.body()).getInt("code") == 200) {
+                        Constant.qaUrl = callbackUrl
+                        ToastUtils.showLong(if (callbackUrl.isEmpty()) "关闭成功" else "更新成功")
+                    } else {
+                        onError(response)
+                    }
+                }
+
+                override fun onError(response: Response<String>?) {
+                    ToastUtils.showLong(if (callbackUrl.isEmpty()) "关闭失败,请稍后再试~" else "更新失败,请稍后再试~")
+                }
+            }
+            OkGo.post<String>(Constant.getRobotUpdateUrl()).upRequestBody(requestBody).execute(call)
+        } catch (e: Exception) {
+            throw RuntimeException(e)
+        }
+    }
+
+    private fun showCorpNameDialog() {
+        val builder = QMUIDialog.EditTextDialogBuilder(this)
+        builder.setTitle("企业名称")
+            .setDefaultText(Constant.weworkCorpName)
+            .setInputType(InputType.TYPE_CLASS_TEXT)
+            .addAction(getString(R.string.cancel)) { dialog, index -> dialog.dismiss() }
+            .addAction(getString(R.string.add)) { dialog, index ->
+                val text = builder.editText.text
+                if (text != null) {
+                    dialog.dismiss()
+                    Constant.weworkCorpName = text.toString().trim()
+                } else {
+                    ToastUtils.showLong("请勿为空!")
+                }
+            }
+            .create(R.style.QMUI_Dialog).show()
+    }
+
+    private fun showCorpIdDialog() {
+        val builder = QMUIDialog.EditTextDialogBuilder(this)
+        builder.setTitle("CorpId")
+            .setDefaultText(Constant.weworkCorpId)
+            .setInputType(InputType.TYPE_CLASS_TEXT)
+            .addAction(getString(R.string.cancel)) { dialog, index -> dialog.dismiss() }
+            .addAction(getString(R.string.add)) { dialog, index ->
+                val text = builder.editText.text
+                if (text != null) {
+                    dialog.dismiss()
+                    Constant.weworkCorpId = text.toString().trim()
+                } else {
+                    ToastUtils.showLong("请勿为空!")
+                }
+            }
+            .create(R.style.QMUI_Dialog).show()
+    }
+
+    private fun showAgentIdDialog() {
+        val builder = QMUIDialog.EditTextDialogBuilder(this)
+        builder.setTitle("AgentId")
+            .setDefaultText(Constant.weworkAgentId)
+            .setInputType(InputType.TYPE_CLASS_TEXT)
+            .addAction(getString(R.string.cancel)) { dialog, index -> dialog.dismiss() }
+            .addAction(getString(R.string.add)) { dialog, index ->
+                val text = builder.editText.text
+                if (text != null) {
+                    dialog.dismiss()
+                    Constant.weworkAgentId = text.toString().trim()
+                } else {
+                    ToastUtils.showLong("请勿为空!")
+                }
+            }
+            .create(R.style.QMUI_Dialog).show()
+    }
+
+    private fun showSchemaDialog() {
+        val builder = QMUIDialog.EditTextDialogBuilder(this)
+        builder.setTitle("Schema")
+            .setDefaultText(Constant.weworkSchema)
+            .setInputType(InputType.TYPE_CLASS_TEXT)
+            .addAction(getString(R.string.cancel)) { dialog, index -> dialog.dismiss() }
+            .addAction(getString(R.string.add)) { dialog, index ->
+                val text = builder.editText.text
+                if (text != null) {
+                    dialog.dismiss()
+                    Constant.weworkSchema = text.toString().trim()
+                    IWWAPIUtil.init(this)
+                } else {
+                    ToastUtils.showLong("请勿为空!")
+                }
+            }
+            .create(R.style.QMUI_Dialog).show()
+    }
+
+    private fun showUserNameDialog() {
+        val builder = QMUIDialog.EditTextDialogBuilder(this)
+        builder.setTitle("UserName")
+            .setDefaultText(Constant.weworkMP)
+            .setInputType(InputType.TYPE_CLASS_TEXT)
+            .addAction(getString(R.string.cancel)) { dialog, index -> dialog.dismiss() }
+            .addAction(getString(R.string.add)) { dialog, index ->
+                val text = builder.editText.text
+                if (text != null) {
+                    dialog.dismiss()
+                    val username = text.toString().trim()
+                    Constant.weworkMP = if (username.endsWith("@app")) username else "$username@app"
+                } else {
+                    ToastUtils.showLong("请勿为空!")
+                }
+            }
+            .create(R.style.QMUI_Dialog).show()
+    }
+
+    private fun showSignatureDialog() {
+        val signature = AppUtils.getAppSignaturesMD5().firstOrNull()?.replace(":", "")?.toLowerCase(Locale.ROOT)
+        val builder = QMUIDialog.EditTextDialogBuilder(this)
+        builder.setTitle("Signature")
+            .setDefaultText(signature)
+            .setInputType(InputType.TYPE_NULL)
+            .addAction(getString(R.string.copy)) { dialog, index ->
+                dialog.dismiss()
+                ClipboardUtils.copyText(signature)
+                ToastUtils.showLong("复制成功")
+            }
+            .create(R.style.QMUI_Dialog).show()
+    }
+
+    private fun updateOldDeviceConfig() {
+        if (Constant.oldDevice) {
+            Constant.LONG_INTERVAL = (Constant.BASE_LONG_INTERVAL * 1.5).toLong()
+            Constant.CHANGE_PAGE_INTERVAL = (Constant.BASE_CHANGE_PAGE_INTERVAL * 1.5).toLong()
+            Constant.POP_WINDOW_INTERVAL = (Constant.BASE_POP_WINDOW_INTERVAL * 1.5).toLong()
+            ToastUtils.showLong("防卡顿模式开启")
+        } else {
+            Constant.LONG_INTERVAL = Constant.BASE_LONG_INTERVAL
+            Constant.CHANGE_PAGE_INTERVAL = Constant.BASE_CHANGE_PAGE_INTERVAL
+            Constant.POP_WINDOW_INTERVAL = Constant.BASE_POP_WINDOW_INTERVAL
+            ToastUtils.showLong("防卡顿模式关闭")
+        }
+    }
+
+}

+ 16 - 0
app/src/main/java/org/yameida/worktool/annotation/RequestMapping.java

@@ -0,0 +1,16 @@
+package org.yameida.worktool.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标识方法为请求接口
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface RequestMapping {
+}

+ 28 - 0
app/src/main/java/org/yameida/worktool/config/GlobalException.java

@@ -0,0 +1,28 @@
+package org.yameida.worktool.config;
+
+import android.util.Log;
+
+import com.blankj.utilcode.util.AppUtils;
+
+public class GlobalException implements Thread.UncaughtExceptionHandler {
+    private final static GlobalException myCrashHandler = new GlobalException();
+
+    private GlobalException() {
+    }
+
+    public static synchronized GlobalException getInstance() {
+        return myCrashHandler;
+    }
+
+    @Override
+    public void uncaughtException(Thread arg0, Throwable arg1) {
+        Log.e("GlobalException", "-------------Caught Exception-------------");
+        arg1.printStackTrace();
+        try {
+            Thread.sleep(3000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        AppUtils.relaunchApp(true);
+    }
+}

+ 162 - 0
app/src/main/java/org/yameida/worktool/model/AppUpdate.java

@@ -0,0 +1,162 @@
+package org.yameida.worktool.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class AppUpdate implements Serializable {
+    private Long id;
+
+//    @ApiModelProperty(value = "应用名称")
+    private String appName;
+
+//    @ApiModelProperty(value = "更新标题")
+    private String title;
+
+//    @ApiModelProperty(value = "更新日志")
+    private String updateLog;
+
+//    @ApiModelProperty(value = "备注")
+    private String remark;
+
+//    @ApiModelProperty(value = "更新版本号")
+    private String versionName;
+
+//    @ApiModelProperty(value = "内部版本号")
+    private Integer versionCode;
+
+//    @ApiModelProperty(value = "强制更新内部版本号")
+    private Integer minVersionCode;
+
+//    @ApiModelProperty(value = "apk链接")
+    private String downloadUrl;
+
+//    @ApiModelProperty(value = "创建时间")
+    private Date createTime;
+
+//    @ApiModelProperty(value = "安装包大小")
+    private String size;
+
+//    @ApiModelProperty(value = "可用")
+    private Boolean enable;
+
+    private static final long serialVersionUID = 1L;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getAppName() {
+        return appName;
+    }
+
+    public void setAppName(String appName) {
+        this.appName = appName;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getUpdateLog() {
+        return updateLog;
+    }
+
+    public void setUpdateLog(String updateLog) {
+        this.updateLog = updateLog;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public String getVersionName() {
+        return versionName;
+    }
+
+    public void setVersionName(String versionName) {
+        this.versionName = versionName;
+    }
+
+    public Integer getVersionCode() {
+        return versionCode;
+    }
+
+    public void setVersionCode(Integer versionCode) {
+        this.versionCode = versionCode;
+    }
+
+    public Integer getMinVersionCode() {
+        return minVersionCode;
+    }
+
+    public void setMinVersionCode(Integer minVersionCode) {
+        this.minVersionCode = minVersionCode;
+    }
+
+    public String getDownloadUrl() {
+        return downloadUrl;
+    }
+
+    public void setDownloadUrl(String downloadUrl) {
+        this.downloadUrl = downloadUrl;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getSize() {
+        return size;
+    }
+
+    public void setSize(String size) {
+        this.size = size;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", appName=").append(appName);
+        sb.append(", title=").append(title);
+        sb.append(", updateLog=").append(updateLog);
+        sb.append(", remark=").append(remark);
+        sb.append(", versionName=").append(versionName);
+        sb.append(", versionCode=").append(versionCode);
+        sb.append(", minVersionCode=").append(minVersionCode);
+        sb.append(", downloadUrl=").append(downloadUrl);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", size=").append(size);
+        sb.append(", enable=").append(enable);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 145 - 0
app/src/main/java/org/yameida/worktool/model/ExecCallbackBean.kt

@@ -0,0 +1,145 @@
+package org.yameida.worktool.model
+
+/**
+ * 任务执行成功失败结果回传
+ */
+data class ExecCallbackBean(
+    //原生消息指令
+    var rawMsg: String? = null,
+
+    //0成功 其他是失败错误码
+    var errorCode: Int? = null,
+
+    //失败原因
+    var errorReason: String? = null,
+
+    //执行时间
+    var runTime: Long? = null,
+
+    //执行耗时
+    var timeCost: Double? = null,
+
+    //成功名单
+    var successList: List<String>? = null,
+
+    //失败名单
+    var failList: List<String>? = null,
+
+    //附加信息
+    var metaJson: String? = null
+
+) : WeworkMessageBean() {
+    companion object {
+        /**
+         * 错误码
+         * 通用格式错误 10xxx
+         * 执行任务错误 20xxx
+         * 服务端返回错误 50xxx
+         */
+        const val SUCCESS = 0
+
+        //数据格式错误
+        const val ERROR_ILLEGAL_DATA = 101011
+
+        //非法操作
+        const val ERROR_ILLEGAL_OPERATION = 101012
+
+        //非法权限
+        const val ERROR_ILLEGAL_PERMISSION = 101013
+
+        //创建群失败
+        const val ERROR_CREATE_GROUP = 201011
+
+        //群改名失败
+        const val ERROR_GROUP_RENAME = 201012
+
+        //群拉人失败
+        const val ERROR_GROUP_ADD_MEMBER = 201013
+
+        //群踢人失败
+        const val ERROR_GROUP_REMOVE_MEMBER = 201014
+
+        //改群公告失败
+        const val ERROR_GROUP_CHANGE_ANNOUNCEMENT = 201015
+
+        //改群备注失败
+        const val ERROR_GROUP_CHANGE_REMARK = 201016
+
+        //改群模板失败
+        const val ERROR_GROUP_TEMPLATE = 201017
+
+        //创建群达到上限/限制
+        const val ERROR_CREATE_GROUP_LIMIT = 201018
+
+        //查找聊天窗失败
+        const val ERROR_INTO_ROOM = 201101
+
+        //发送消息失败
+        const val ERROR_SEND_MESSAGE = 201102
+
+        //按钮寻找失败
+        const val ERROR_BUTTON = 201103
+
+        //目标寻找失败
+        const val ERROR_TARGET = 201104
+
+        //转发失败
+        const val ERROR_RELAY = 201105
+
+        //重复添加
+        const val ERROR_REPEAT = 201106
+
+        //文件下载异常
+        const val ERROR_FILE_DOWNLOAD = 201107
+
+        //文件存储异常
+        const val ERROR_FILE_STORAGE = 201108
+
+        //防骚扰搜索异常
+        const val ERROR_SEARCH_ALARM = 301000
+
+        //机器人id错误
+        const val ERROR_INVALID_ROBOT_ID = 501011
+
+        //机器人key错误
+        const val ERROR_INVALID_ROBOT_KEY = 501012
+
+        //机器人重复登录
+        const val ERROR_ROBOT_MULTI_LOGIN = 501013
+
+        //机器人其他未知错误
+        const val ERROR_OTHER = 501000
+
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+        if (!super.equals(other)) return false
+
+        other as ExecCallbackBean
+
+        if (rawMsg != other.rawMsg) return false
+        if (errorCode != other.errorCode) return false
+        if (errorReason != other.errorReason) return false
+        if (runTime != other.runTime) return false
+        if (timeCost != other.timeCost) return false
+        if (successList != other.successList) return false
+        if (failList != other.failList) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + (rawMsg?.hashCode() ?: 0)
+        result = 31 * result + (errorCode ?: 0)
+        result = 31 * result + (errorReason?.hashCode() ?: 0)
+        result = 31 * result + (runTime?.hashCode() ?: 0)
+        result = 31 * result + (timeCost?.hashCode() ?: 0)
+        result = 31 * result + (successList?.hashCode() ?: 0)
+        result = 31 * result + (failList?.hashCode() ?: 0)
+        return result
+    }
+
+}

+ 21 - 0
app/src/main/java/org/yameida/worktool/model/MyConfigBean.kt

@@ -0,0 +1,21 @@
+package org.yameida.worktool.model
+
+data class MyConfigBean(
+    //QA类型 0未配置 1第三方QA 2微信对话平台
+    var openCallback: Int? = null,
+
+    //第三方QA回调地址
+    var callbackUrl: String? = null,
+
+    //回复策略 -1只读消息不回调 0仅私聊和群聊@机器人回调 1私聊群聊全部回调
+    var replyAll: Int? = null,
+
+    //key校验 0未开启 1开启
+    var robotKeyCheck: Int? = null,
+
+    //通讯加密 0不加密 1加密
+    var encryptType: Int? = null,
+
+    //创建时间
+    var createTime: String? = null
+)

文件差異過大導致無法顯示
+ 534 - 0
app/src/main/java/org/yameida/worktool/model/WeworkMessageBean.java


+ 93 - 0
app/src/main/java/org/yameida/worktool/model/WeworkMessageListBean.kt

@@ -0,0 +1,93 @@
+package org.yameida.worktool.model
+
+import com.blankj.utilcode.util.EncryptUtils
+import com.blankj.utilcode.util.GsonUtils
+import com.blankj.utilcode.util.TimeUtils
+import org.yameida.worktool.Constant
+import java.util.*
+
+class WeworkMessageListBean<T> {
+
+    companion object {
+        const val SOCKET_TYPE_HEARTBEAT = 0
+        const val SOCKET_TYPE_MESSAGE_CONFIRM = 1
+        const val SOCKET_TYPE_MESSAGE_LIST = 2
+        const val SOCKET_TYPE_RAW_CONFIRM = 3
+        const val SOCKET_TYPE_INTI = 4
+    }
+
+    /**
+     * type
+     * TYPE_HEARTBEAT 心跳检测
+     * TYPE_MESSAGE_CONFIRM 消息确认
+     * TYPE_MESSAGE_LIST 消息列表
+     * SOCKET_TYPE_RAW_CONFIRM 执行指令结果确认
+     */
+    var socketType = SOCKET_TYPE_HEARTBEAT
+
+    //消息id
+    var messageId = TimeUtils.date2String(Date()).replace(" ", "#") + "#" + UUID.randomUUID()
+
+    var meta: String? = null
+
+    //api类型 0=后台消息 1=API指令调用
+    var apiSend: Int? = null
+
+    //消息列表
+    var list: ArrayList<T> = arrayListOf()
+
+    //加密消息列表
+    var encryptedList: String = ""
+
+    //消息加密 0不加密 1AES
+    var encryptType = Constant.encryptType
+
+    var action: String? = ""
+
+    var weworkSchema: String? = ""
+
+    constructor(weworkMessageBean: T, type: Int, messageId: String? = null, meta: String? = null) {
+        if (encryptType == 0) {
+            list.add(weworkMessageBean)
+        } else if (encryptType == 1) {
+            encryptedList = EncryptUtils.encryptAES2HexString(
+                GsonUtils.toJson(arrayListOf(weworkMessageBean)).toByteArray(),
+                Constant.key,
+                Constant.transformation,
+                Constant.iv
+            )
+        }
+        this.socketType = type
+        if (messageId != null) this.messageId = messageId
+        if (meta != null) this.meta = meta
+    }
+
+    constructor(
+        weworkMessageBean: T,
+        type: Int,
+        messageId: String? = null,
+        meta: String? = null,
+        action: String? = null
+    ) {
+        if (encryptType == 0) {
+            list.add(weworkMessageBean)
+        } else if (encryptType == 1) {
+            encryptedList = EncryptUtils.encryptAES2HexString(
+                GsonUtils.toJson(arrayListOf(weworkMessageBean)).toByteArray(),
+                Constant.key,
+                Constant.transformation,
+                Constant.iv
+            )
+        }
+        this.socketType = type
+        if (messageId != null) this.messageId = messageId
+        if (action != null) this.action = action
+        if (meta != null) this.meta = meta
+    }
+
+    constructor(messageId: String, type: Int) {
+        this.messageId = messageId
+        this.socketType = type
+    }
+
+}

+ 11 - 0
app/src/main/java/org/yameida/worktool/model/network/CheckUpdateResult.java

@@ -0,0 +1,11 @@
+package org.yameida.worktool.model.network;
+
+import org.yameida.worktool.model.AppUpdate;
+
+public class CheckUpdateResult {
+
+    public Integer code;
+    public String message;
+    public AppUpdate data;
+
+}

+ 11 - 0
app/src/main/java/org/yameida/worktool/model/network/GetMyConfigResult.java

@@ -0,0 +1,11 @@
+package org.yameida.worktool.model.network;
+
+import org.yameida.worktool.model.MyConfigBean;
+
+public class GetMyConfigResult {
+
+    public Integer code;
+    public String message;
+    public MyConfigBean data;
+
+}

+ 11 - 0
app/src/main/java/org/yameida/worktool/model/operation/SelectResult.kt

@@ -0,0 +1,11 @@
+package org.yameida.worktool.model.operation
+
+class SelectResult {
+
+    var successList = arrayListOf<String>()
+
+    var failList = arrayListOf<String>()
+
+    var result = false
+
+}

+ 36 - 0
app/src/main/java/org/yameida/worktool/notification/PlayNotifyManager.kt

@@ -0,0 +1,36 @@
+package org.yameida.worktool.notification
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import com.blankj.utilcode.util.Utils
+import org.yameida.worktool.service.PlayNotifyService
+import org.yameida.worktool.utils.startServiceSafe
+import java.lang.reflect.Method
+
+
+/**
+ * Created by Gallon on 2019/8/7.
+ */
+object PlayNotifyManager {
+    private val TAG = PlayNotifyManager::class.java.simpleName
+
+    private var context = Utils.getApp()
+    @SuppressLint("WrongConstant")
+    private val statusBarManager = context.getSystemService("statusbar")
+
+    var isShow = false
+
+    fun show() {
+        context.startServiceSafe(Intent(context, PlayNotifyService::class.java))
+    }
+
+    private var collapse: Method? = null
+    fun collapseStatusBar() {
+        try {
+            if (collapse == null) collapse = statusBarManager::class.java.getMethod("collapsePanels").apply { isAccessible = true }
+            collapse?.invoke(statusBarManager)
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+}

+ 162 - 0
app/src/main/java/org/yameida/worktool/observer/MultiFileObserver.java

@@ -0,0 +1,162 @@
+package org.yameida.worktool.observer;
+
+import android.os.FileObserver;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Stack;
+
+public class MultiFileObserver extends FileObserver {
+
+    public HashMap<String, Long> map = new HashMap<>();
+    public static HashSet<String> createSet = new LinkedHashSet<>();
+    public static HashSet<String> finishSet = new LinkedHashSet<>();
+    public static HashSet<String> saveSet = new LinkedHashSet<>();
+
+    /** Only modification events */
+    public static int CHANGES_ONLY = CREATE | MODIFY | DELETE | CLOSE_WRITE
+            | DELETE_SELF | MOVE_SELF | MOVED_FROM | MOVED_TO;
+
+    private List<SingleFileObserver> mObservers;
+    private String mPath;
+    private int mMask;
+
+    public MultiFileObserver(String path) {
+        this(path, ALL_EVENTS);
+    }
+
+    public MultiFileObserver(String path, int mask) {
+        super(path, mask);
+        mPath = path;
+        mMask = mask;
+    }
+
+    @Override
+    public void startWatching() {
+        if (mObservers != null)
+            return;
+
+        mObservers = new ArrayList<SingleFileObserver>();
+        Stack<String> stack = new Stack<String>();
+        stack.push(mPath);
+
+        while (!stack.isEmpty()) {
+            String parent = stack.pop();
+            mObservers.add(new SingleFileObserver(parent, mMask));
+            File path = new File(parent);
+            File[] files = path.listFiles();
+            if (null == files)
+                continue;
+            for (File f : files) {
+                if (f.isDirectory() && !f.getName().equals(".")
+                        && !f.getName().equals("..")) {
+                    stack.push(f.getPath());
+                }
+            }
+        }
+
+        for (int i = 0; i < mObservers.size(); i++) {
+            SingleFileObserver sfo =  mObservers.get(i);
+            sfo.startWatching();
+        }
+    }
+
+    @Override
+    public void stopWatching() {
+        if (mObservers == null)
+            return;
+
+        for (int i = 0; i < mObservers.size(); i++) {
+            SingleFileObserver sfo = mObservers.get(i);
+            sfo.stopWatching();
+        }
+
+        mObservers.clear();
+        mObservers = null;
+    }
+
+    @Override
+    public void onEvent(int event, String path) {
+        switch (event) {
+            case FileObserver.ACCESS:
+            case FileObserver.CLOSE_WRITE:
+            case FileObserver.CLOSE_NOWRITE:
+                if (path.endsWith(".0") && !map.containsKey(path) && createSet.contains(path)) {
+                    map.put(path, System.currentTimeMillis());
+                    Log.i("RecursiveFileObserver", "发现新图片: " + path);
+                    finishSet.add(path);
+//                    try {
+//                        ImageInfo imageInfo = new ImageInfo();
+//                        imageInfo.setDetermineImageNumber(true);
+//                        ImageInfo.run(path, new FileInputStream(path), imageInfo, true);
+//                    } catch (Exception e) { e.printStackTrace(); }
+                }
+                break;
+            case FileObserver.CREATE:
+                Log.i("RecursiveFileObserver", "CREATE: " + path);
+                if (path.endsWith(".0")) {
+                    createSet.add(path);
+                }
+                break;
+            case FileObserver.ATTRIB:
+//                Log.i("RecursiveFileObserver", "ATTRIB: " + path);
+                break;
+//            case FileObserver.CLOSE_WRITE:
+//                Log.i("RecursiveFileObserver", "CLOSE_WRITE: " + path);
+//                break;
+            case FileObserver.DELETE:
+//                Log.i("RecursiveFileObserver", "DELETE: " + path);
+                break;
+            case FileObserver.DELETE_SELF:
+//                Log.i("RecursiveFileObserver", "DELETE_SELF: " + path);
+                break;
+            case FileObserver.MODIFY:
+//                Log.i("RecursiveFileObserver", "MODIFY: " + path);
+                break;
+            case FileObserver.MOVE_SELF:
+//                Log.i("RecursiveFileObserver", "MOVE_SELF: " + path);
+                break;
+            case FileObserver.MOVED_FROM:
+//                Log.i("RecursiveFileObserver", "MOVED_FROM: " + path);
+                break;
+            case FileObserver.MOVED_TO:
+//                Log.i("RecursiveFileObserver", "MOVED_TO: " + path);
+                break;
+            case FileObserver.OPEN:
+//                Log.i("RecursiveFileObserver", "OPEN: " + path);
+                break;
+            default:
+//                Log.i("RecursiveFileObserver", "DEFAULT(" + event + " : " + path);
+                break;
+        }
+    }
+
+    /**
+     * Monitor single directory and dispatch all events to its parent, with full
+     * path.
+     */
+    class SingleFileObserver extends FileObserver {
+        String mPath;
+
+        public SingleFileObserver(String path) {
+            this(path, ALL_EVENTS);
+            mPath = path;
+        }
+
+        public SingleFileObserver(String path, int mask) {
+            super(path, mask);
+            mPath = path;
+        }
+
+        @Override
+        public void onEvent(int event, String path) {
+            String newPath = mPath + "/" + path;
+            MultiFileObserver .this.onEvent(event, newPath);
+        }
+    }
+}

+ 343 - 0
app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt

@@ -0,0 +1,343 @@
+package org.yameida.worktool.service
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.view.accessibility.AccessibilityNodeInfo
+import com.blankj.utilcode.util.*
+import com.hjq.toast.ToastUtils
+import org.yameida.worktool.Constant
+import org.yameida.worktool.MyApplication
+import org.yameida.worktool.model.ExecCallbackBean
+import org.yameida.worktool.model.WeworkMessageBean
+import org.yameida.worktool.model.WeworkMessageListBean
+import org.yameida.worktool.utils.AccessibilityUtil
+import org.yameida.worktool.utils.FloatWindowHelper
+import org.yameida.worktool.utils.Views
+
+var requestCode = 1000000
+fun fastStartActivity(
+    context: Context,
+    clazz: Class<*>,
+    flags: Int = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP,
+    i: Intent? = null
+) {
+    val intent = i ?: Intent(context, clazz)
+    intent.flags = flags
+    val pendingIntent = PendingIntent.getActivity(context, requestCode++, intent, 0)
+    try {
+        pendingIntent.send()
+    } catch (e: PendingIntent.CanceledException) {
+        e.printStackTrace()
+        context.startActivity(intent)
+    }
+}
+
+/**
+ * 进入首页-消息页
+ */
+fun goHome() {
+    goHomeTab("消息")
+}
+
+/**
+ * 进入首页tab
+ * 1.检查是否有底部tab
+ * 2.回退到首页
+ * @param title 消息/文档/通讯录/工作台/我
+ * 可能因为管理员排版首页Tab而导致找不到匹配title
+ */
+fun goHomeTab(title: String): Boolean {
+    var find = false
+    while (!find) {
+        val list = AccessibilityUtil.findAllOnceByText(getRoot(), title, exact = true)
+        for (item in list) {
+            val childCount = item.parent?.parent?.parent?.childCount
+            if (childCount == 4 || childCount == 5) {
+                //处理侧边栏抽屉打开
+                if (title == "消息") {
+                    val rect = Rect()
+                    item.getBoundsInScreen(rect)
+                    if (rect.left > ScreenUtils.getScreenWidth() / 2) {
+                        return goHomeTab("工作台") && goHomeTab("消息")
+                    }
+                }
+                if (!item.isSelected) {
+                    AccessibilityUtil.performClick(item)
+                    sleep(300)
+                }
+                find = true
+            }
+        }
+        if (!find) {
+            if (isAtHome()) {
+                return false
+            } else {
+                backPress()
+                //如果在登录页面就提示关闭worktool主功能
+                if (AccessibilityUtil.findOnceByText(getRoot(), "手机号登录", exact = true) != null) {
+                    LogUtils.e("登录前请先关闭WorkTool主功能!")
+                    ToastUtils.show("登录前请先关闭WorkTool主功能!")
+                    WeworkController.weworkService.disableSelf()
+                    WeworkController.weworkService.webSocketManager.close(1000, "wework logout")
+                    MyApplication.launchIntent()
+                    sleep(5000)
+                }
+            }
+        }
+    }
+    LogUtils.v("进入首页-${title}页")
+    return find
+}
+
+/**
+ * 当前是否在首页
+ */
+fun isAtHome(): Boolean {
+    val list = AccessibilityUtil.findAllOnceByText(getRoot(), "消息", exact = true)
+    val item = list.firstOrNull {
+        val childCount = it.parent?.parent?.parent?.childCount
+        (childCount == 4 || childCount == 5)
+    } ?: return false
+    if (!item.isSelected) {
+        AccessibilityUtil.performClick(item)
+        sleep(300)
+    }
+    return true
+}
+
+/**
+ * 获取企业微信窗口
+ */
+fun getRoot(): AccessibilityNodeInfo {
+    return getRoot(false)
+}
+
+/**
+ * 获取前台窗口
+ * @param ignoreCheck false 必须等待前台为企业微信 true 直接返回当前前台窗口
+ */
+fun getRoot(ignoreCheck: Boolean, share: Boolean = false): AccessibilityNodeInfo {
+    while (true) {
+        val tempRoot = WeworkController.weworkService.rootInActiveWindow
+        val root = WeworkController.weworkService.rootInActiveWindow
+        if (tempRoot != root) {
+            LogUtils.e("tempRoot != root")
+        } else if (root != null) {
+            if (root.packageName == Constant.PACKAGE_NAMES) {
+                return root
+            } else {
+                LogUtils.e("当前不在企业微信: ${root.packageName}")
+                if (root.packageName == "com.android.systemui") {
+                    val tvProjection = AccessibilityUtil.findOnceByText(root, "立即开始", exact = true)
+                    if (tvProjection != null) {
+                        AccessibilityUtil.performClick(tvProjection)
+                        LogUtils.i("点击立即开始投屏")
+                        log("点击立即开始投屏")
+                    }
+                }
+                if (root.packageName == "android") {
+                    val tvANR = AccessibilityUtil.findOnceByText(root, "关闭应用", exact = true)
+                    if (tvANR != null) {
+                        AccessibilityUtil.performClick(tvANR)
+                        LogUtils.e("点击关闭应用ANR")
+                        error("点击关闭应用ANR")
+                    }
+                }
+                //app外场景禁止自动跳回(分享文件)
+                if (!share) {
+                    WeworkController.weworkService.currentPackage =
+                        root.packageName?.toString() ?: ""
+                    if (System.currentTimeMillis() % 30 == 0L) {
+                        LogUtils.e("当前不在企业微信: ${root.packageName}")
+                        error("当前不在企业微信: ${root.packageName}")
+                        if (!FloatWindowHelper.isPause) {
+                            LogUtils.e("当前不在企业微信: ${root.packageName}\n尝试跳转到企业微信")
+                            ToastUtils.show("当前不在企业微信: ${root.packageName}\n尝试跳转到企业微信")
+                            Utils.getApp().packageManager.getLaunchIntentForPackage(Constant.PACKAGE_NAMES)
+                                ?.apply {
+                                    this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                                    Utils.getApp().startActivity(this)
+                                }
+                        }
+                    }
+                }
+                if (ignoreCheck) {
+                    return root
+                }
+            }
+        }
+        sleep(Constant.CHANGE_PAGE_INTERVAL)
+    }
+}
+
+/**
+ * 后退
+ */
+fun backPress() {
+    val clazz = WeworkController.weworkService.currentClass
+    val root = getRoot()
+    val textView = AccessibilityUtil.findOnceByClazz(root, Views.TextView)
+    if (textView != null && textView.text.isNullOrBlank() && AccessibilityUtil.performClick(
+            textView,
+            retry = false
+        )
+    ) {
+        LogUtils.v("找到回退按钮")
+    } else {
+        if (AccessibilityUtil.findOnceByText(root, "移出成员") != null) {
+            val list = AccessibilityUtil.findOneByClazz(root, Views.RecyclerView, Views.ListView)
+            if (list != null) {
+                val frontNode = AccessibilityUtil.findFrontNode(list)
+                val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView)
+                if (textViewList.size >= 2) {
+                    val closeButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1]
+                    AccessibilityUtil.performClick(closeButton)
+                }
+            }
+            return
+        }
+        val ivButton = AccessibilityUtil.findOnceByClazz(root, Views.ImageView)
+        if (ivButton != null && ivButton.isClickable && AccessibilityUtil.findFrontNode(ivButton) == null) {
+            LogUtils.d("未找到回退按钮 点击第一个IV按钮")
+            AccessibilityUtil.performClick(ivButton, retry = false)
+        } else {
+            LogUtils.d("未找到回退按钮 点击第一个BT按钮")
+            val button = AccessibilityUtil.findOnceByClazz(root, Views.Button)
+            if (button != null && button.childCount > 0) {
+                AccessibilityUtil.performClick(button.getChild(0), retry = false)
+            } else if (button != null) {
+                AccessibilityUtil.performClick(button, retry = false)
+            } else {
+                LogUtils.d("未找到BT按钮")
+                val confirm = AccessibilityUtil.findOnceByText(
+                    root,
+                    "不保存",
+                    "确定",
+                    "我知道了",
+                    "暂不进入",
+                    "不用了",
+                    "取消",
+                    "暂不",
+                    "关闭",
+                    "退出",
+                    "留在企业微信",
+                    exact = true
+                )
+                if (confirm != null) {
+                    LogUtils.d("尝试点击确定/我知道了/暂不进入")
+                    AccessibilityUtil.performClick(confirm)
+                } else {
+                    val stayButton =
+                        AccessibilityUtil.findOnceByText(getRoot(true), "关闭应用", "等待", exact = true)
+                    if (stayButton != null) {
+                        LogUtils.d("疑似ANR 尝试点击等待")
+                        AccessibilityUtil.performClick(stayButton)
+                    } else {
+                        LogUtils.d("未找到对话框 点击bar中心")
+                        AccessibilityUtil.performXYClick(
+                            WeworkController.weworkService,
+                            ScreenUtils.getScreenWidth() / 2F,
+                            BarUtils.getStatusBarHeight() * 2F
+                        )
+                        sleep(Constant.CHANGE_PAGE_INTERVAL * 2)
+                        if (WeworkController.weworkService.currentClass == clazz) {
+                            val firstEmptyTextView =
+                                AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView)
+                                    .firstOrNull { it.text.isNullOrEmpty() }
+                            if (firstEmptyTextView != null && firstEmptyTextView.isClickable) {
+                                AccessibilityUtil.performClick(firstEmptyTextView)
+                            }
+                            sleep(Constant.CHANGE_PAGE_INTERVAL)
+                            if (WeworkController.weworkService.currentClass == clazz
+                                && WeworkController.weworkService.currentPackage == Constant.PACKAGE_NAMES
+                            ) {
+                                AccessibilityUtil.globalGoBack(WeworkController.weworkService)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    sleep(Constant.POP_WINDOW_INTERVAL)
+}
+
+/**
+ * 上传执行指令结果
+ */
+fun uploadCommandResult(
+    message: WeworkMessageBean,
+    errorCode: Int,
+    errorReason: String,
+    startTime: Long,
+    successList: List<String> = listOf(),
+    failList: List<String> = listOf(),
+    metaJson: String? = null
+) {
+    if ((message.fileBase64?.length ?: 0) > 100) {
+        message.fileBase64 = message.fileBase64.substring(0, 100)
+    }
+    WeworkController.weworkService.webSocketManager.send(
+        WeworkMessageListBean(
+            ExecCallbackBean(
+                GsonUtils.toJson(message),
+                errorCode,
+                errorReason,
+                startTime,
+                (System.currentTimeMillis() - startTime) / 1000.0,
+                successList,
+                failList,
+                metaJson
+            ),
+            WeworkMessageListBean.SOCKET_TYPE_RAW_CONFIRM,
+            messageId = message.messageId,
+            action = message.action,
+            meta = message.meta
+        ), true
+    )
+    if (errorCode != 0) {
+        if (message.apiSend == 1) {
+            ToastUtils.show("错误提示 错误码: $errorCode 错误信息: $errorReason")
+        }
+        LogUtils.v("错误提示 错误码: $errorCode 错误信息: $errorReason")
+    }
+}
+
+/**
+ * 上传运行日志
+ */
+fun log(message: Any?, type: Int = WeworkMessageBean.ROBOT_LOG) {
+    WeworkController.weworkService.webSocketManager.send(
+        WeworkMessageListBean(
+            WeworkMessageBean(
+                null, null,
+                type,
+                null,
+                null,
+                null,
+                if (message is String) message else GsonUtils.toJson(message)
+            ),
+            WeworkMessageListBean.SOCKET_TYPE_MESSAGE_LIST
+        ), true
+    )
+}
+
+/**
+ * 上传运行日志
+ */
+fun error(message: Any?) {
+    log(message, WeworkMessageBean.ROBOT_ERROR_LOG)
+}
+
+/**
+ * 简单封装 sleep
+ */
+fun sleep(time: Long) {
+    try {
+        Thread.sleep(time)
+    } catch (e: Exception) {
+        e.printStackTrace()
+    }
+}

+ 310 - 0
app/src/main/java/org/yameida/worktool/service/MyLooper.kt

@@ -0,0 +1,310 @@
+package org.yameida.worktool.service
+
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+import com.blankj.utilcode.util.EncryptUtils
+import com.blankj.utilcode.util.GsonUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.SPUtils
+import com.google.gson.reflect.TypeToken
+import okhttp3.WebSocket
+import org.yameida.worktool.Constant
+import org.yameida.worktool.model.ExecCallbackBean
+import org.yameida.worktool.model.WeworkMessageBean
+import org.yameida.worktool.model.WeworkMessageListBean
+import org.yameida.worktool.utils.FloatWindowHelper
+import org.yameida.worktool.utils.IWWAPIUtil
+import org.yameida.worktool.utils.StringFeatureUtil
+import java.nio.charset.StandardCharsets
+import java.util.*
+import kotlin.concurrent.thread
+
+object MyLooper {
+
+    private var threadHandler: Handler? = null
+
+    val looper = thread {
+        LogUtils.i("myLooper starting...")
+        Looper.prepare()
+        val myLooper = Looper.myLooper()
+        if (myLooper != null) {
+            threadHandler = object : Handler(myLooper) {
+                override fun handleMessage(msg: Message) {
+                    while (FloatWindowHelper.isPause) {
+                        LogUtils.i("主功能暂停...")
+                        sleep(Constant.CHANGE_PAGE_INTERVAL)
+                    }
+                    LogUtils.d("handle message: " + Thread.currentThread().name, msg)
+                    try {
+                        dealWithMessage(msg.obj as WeworkMessageBean)
+                    } catch (e: Exception) {
+                        LogUtils.e(e)
+                        error("执行异常尝试重试 ${e.message}")
+                        try {
+                            goHome()
+                            dealWithMessage(msg.obj as WeworkMessageBean)
+                        } catch (e: Exception) {
+                            LogUtils.e(e)
+                            error("执行异常重试仍失败 ${e.message}")
+                            uploadCommandResult(
+                                msg.obj as WeworkMessageBean,
+                                ExecCallbackBean.ERROR_ILLEGAL_OPERATION,
+                                e.message ?: "",
+                                0L
+                            )
+                        }
+                    }
+                }
+            }
+        } else {
+            LogUtils.e("myLooper is null!")
+        }
+        Looper.loop()
+    }
+
+    fun init() {
+        LogUtils.i("init myLooper...")
+        SPUtils.getInstance("noTipMessage").clear()
+        SPUtils.getInstance("lastSyncMessage").clear()
+        SPUtils.getInstance("noSyncMessage").clear()
+        SPUtils.getInstance("limit").clear()
+        SPUtils.getInstance("groupInvite").clear()
+        SPUtils.getInstance("lastImage").clear()
+        SPUtils.getInstance("myInfo").clear()
+    }
+
+    fun getInstance(): Handler {
+        while (true) {
+            threadHandler?.let { return it }
+            LogUtils.e("threadHandler is not ready...")
+            sleep(Constant.POP_WINDOW_INTERVAL / 5)
+        }
+    }
+
+    fun onMessage(webSocket: WebSocket?, text: String) {
+        val messageList: WeworkMessageListBean<WeworkMessageBean> =
+            GsonUtils.fromJson<WeworkMessageListBean<WeworkMessageBean>>(
+                text,
+                object : TypeToken<WeworkMessageListBean<ExecCallbackBean>>() {}.type
+            )
+        if (messageList.socketType == WeworkMessageListBean.SOCKET_TYPE_HEARTBEAT) {
+            return
+        }
+        if (messageList.socketType == WeworkMessageListBean.SOCKET_TYPE_MESSAGE_CONFIRM) {
+            return
+        }
+        if (messageList.socketType == WeworkMessageListBean.SOCKET_TYPE_INTI) {
+            if (messageList.weworkSchema != SPUtils.getInstance().getString("weworkSchema")) {
+                SPUtils.getInstance().put("weworkSchema", messageList.weworkSchema)
+                IWWAPIUtil.init()
+            }
+            WeworkController.enableLoopRunning = true
+            getInstance().removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE)
+            getInstance().sendMessage(Message.obtain().apply {
+                what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
+                obj = WeworkMessageBean().apply {
+                    type = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
+                }
+            })
+            return
+        }
+        if (messageList.socketType == WeworkMessageListBean.SOCKET_TYPE_MESSAGE_LIST) {
+            val confirm =
+                WeworkController.weworkService.webSocketManager.confirm(messageList)
+            if (!confirm) return
+            if (messageList.encryptType == 1) {
+                val decryptHexStringAES = EncryptUtils.decryptHexStringAES(
+                    messageList.encryptedList,
+                    Constant.key,
+                    Constant.transformation,
+                    Constant.iv
+                )
+                messageList.list =
+                    GsonUtils.fromJson(
+                        String(decryptHexStringAES, StandardCharsets.UTF_8),
+                        object : TypeToken<ArrayList<WeworkMessageBean>>() {}.type
+                    )
+            }
+            val list =
+                if (Constant.duplicationFilter) LinkedHashSet(messageList.list).toList() else messageList.list
+            //去重处理 丢弃之前的重复指令 丢弃之前的获取新消息指令
+            for (message in list) {
+                if (message.type == WeworkMessageBean.ROBOT_QUEUE_CLEAR) {
+                    getInstance().removeCallbacksAndMessages(null)
+                    LogUtils.i("清空全部待执行指令")
+                } else if (message.type == WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE) {
+                    WeworkController.enableLoopRunning = true
+                } else {
+                    WeworkController.mainLoopRunning = false
+                    LogUtils.v(
+                        "加入指令到执行队列",
+                        if (message.fileBase64.isNullOrEmpty()) GsonUtils.toJson(message) else message.type
+                    )
+                    val messageWhat = StringFeatureUtil.generateFeatureValue(text)
+                    if (Constant.duplicationFilter) {
+                        getInstance().removeMessages(messageWhat)
+                    }
+                    getInstance().sendMessage(Message.obtain().apply {
+                        what = messageWhat
+                        obj = message.apply {
+                            messageId = messageList.messageId
+                            action = messageList.action
+                            meta = messageList.meta
+                            apiSend = messageList.apiSend
+                        }
+                    })
+                }
+                getInstance().removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE)
+                getInstance().sendMessage(Message.obtain().apply {
+                    what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
+                    obj = WeworkMessageBean().apply {
+                        type = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
+                    }
+                })
+            }
+        }
+    }
+
+    private fun dealWithMessage(message: WeworkMessageBean) {
+        when (message.type) {
+            WeworkMessageBean.TYPE_CONSOLE_TOAST -> {
+                WeworkController.consoleToast(message as ExecCallbackBean)
+            }
+            WeworkMessageBean.STOP_AND_GO_HOME -> {
+                WeworkController.stopAndGoHome()
+            }
+            WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE -> {
+                WeworkController.loopReceiveNewMessage()
+            }
+            WeworkMessageBean.SEND_MESSAGE -> {
+                WeworkController.sendMessage(message)
+            }
+            WeworkMessageBean.REPLY_MESSAGE -> {
+                WeworkController.replyMessage(message)
+            }
+            WeworkMessageBean.RELAY_MESSAGE -> {
+                WeworkController.relayMessage(message)
+            }
+            WeworkMessageBean.INIT_GROUP -> {
+                WeworkController.initGroup(message)
+            }
+            WeworkMessageBean.UPDATE_GROUP -> {
+                WeworkController.updateGroup(message)
+            }
+            WeworkMessageBean.PUSH_MICRO_DISK_IMAGE -> {
+                WeworkController.pushMicroDiskImage(message)
+            }
+            WeworkMessageBean.PUSH_MICRO_DISK_FILE -> {
+                WeworkController.pushMicroDiskFile(message)
+            }
+            WeworkMessageBean.PUSH_MICROPROGRAM -> {
+                WeworkController.pushMicroprogram(message)
+            }
+            WeworkMessageBean.PUSH_OFFICE -> {
+                WeworkController.pushOffice(message)
+            }
+            WeworkMessageBean.PASS_ALL_FRIEND_REQUEST -> {
+            }
+            WeworkMessageBean.ADD_FRIEND_BY_PHONE -> {
+                WeworkController.addFriendByPhone(message)
+            }
+            WeworkMessageBean.PUSH_FILE -> {
+                WeworkController.pushFile(message)
+            }
+            WeworkMessageBean.PUSH_LINK -> {
+                WeworkController.pushLink(message)
+            }
+            WeworkMessageBean.RECALL_MESSAGE -> {
+                WeworkController.recallMessage(message)
+            }
+            WeworkMessageBean.RELAY_MULTI_MESSAGE -> {
+                WeworkController.relayMultiMessage(message)
+            }
+            WeworkMessageBean.RELAY_MERGE_MESSAGE -> {
+                WeworkController.relayMergeMessage(message)
+            }
+            WeworkMessageBean.SEND_MULTI_MESSAGE -> {
+                WeworkController.sendMultiMessage(message)
+            }
+            WeworkMessageBean.SEND_MERGE_MESSAGE -> {
+                WeworkController.sendMergeMessage(message)
+            }
+            WeworkMessageBean.SCAN_QR_CODE -> {
+                WeworkController.scanQrCode(message)
+            }
+            WeworkMessageBean.DELETE_CONTACT -> {
+                WeworkController.deleteContact(message)
+            }
+            WeworkMessageBean.DISMISS_GROUP -> {
+                WeworkController.dismissGroup(message)
+            }
+            WeworkMessageBean.ADD_FRIEND_BY_GROUP -> {
+                WeworkController.addFriendByGroup(message)
+            }
+            WeworkMessageBean.MODIFY_GROUP_MEMBER_INFO -> {
+                WeworkController.modifyGroupMemberInfo(message)
+            }
+            WeworkMessageBean.ADD_NEED_DEAL -> {
+                WeworkController.addNeedDeal(message)
+            }
+            WeworkMessageBean.CLOCK_IN -> {
+                WeworkController.clockIn(message)
+            }
+            WeworkMessageBean.SWITCH_CORP -> {
+                WeworkController.switchCorp(message)
+            }
+            WeworkMessageBean.SHOW_GROUP_INFO -> {
+                WeworkController.showGroupInfo(message)
+            }
+            WeworkMessageBean.GET_GROUP_INFO -> {
+                WeworkController.getGroupInfo(message)
+            }
+            WeworkMessageBean.GET_FRIEND_INFO -> {
+                WeworkController.getFriendInfo(message)
+            }
+            WeworkMessageBean.GET_MY_INFO -> {
+                WeworkController.getMyInfo(message)
+            }
+            WeworkMessageBean.GET_RECENT_LIST -> {
+                WeworkController.getRecentList(message)
+            }
+            WeworkMessageBean.GET_ALL_FRIEND_INFO -> {
+                WeworkController.getAllFriendInfo(message)
+            }
+            WeworkMessageBean.GET_ALL_GROUP_INFO -> {
+                WeworkController.getAllGroupInfo(message)
+            }
+            WeworkMessageBean.GET_LOCAL_FILE -> {
+                WeworkController.getLocalFile(message)
+            }
+            WeworkMessageBean.GET_CORP_LIST -> {
+                WeworkController.getCorpList(message)
+            }
+            WeworkMessageBean.ROBOT_CONTROLLER_TEST -> {
+                WeworkController.test(message)
+            }
+            WeworkMessageBean.TRANSFER_GROUP -> {
+                WeworkController.transferGroup(message)
+            }
+            WeworkMessageBean.ADMINISTRATORS -> {
+                WeworkController.administrators(message)
+            }
+            WeworkMessageBean.REMOVE_ADMINISTRATORS -> {
+                WeworkController.removeAdministrators(message)
+            }
+            WeworkMessageBean.ADD_HARASS_RULE -> {
+                WeworkController.setHarassRules(message)
+            }
+            WeworkMessageBean.DELETE_HARASS_RULE -> {
+                WeworkController.deleteHarassRules(message)
+            }
+            WeworkMessageBean.CANCEL_HARASS_RULE -> {
+                WeworkController.cancelHarassRules(message)
+            }
+            WeworkMessageBean.APPLY_HARASS_RULE -> {
+                WeworkController.applyHarassRules(message)
+            }
+        }
+    }
+}

+ 107 - 0
app/src/main/java/org/yameida/worktool/service/PlayNotifyService.kt

@@ -0,0 +1,107 @@
+package org.yameida.worktool.service
+
+import android.app.*
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.Color
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.Build
+import android.os.IBinder
+import android.os.SystemClock
+import android.widget.RemoteViews
+import androidx.core.app.NotificationCompat
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.Utils
+import org.yameida.worktool.R
+import org.yameida.worktool.activity.GetScreenShotActivity
+import org.yameida.worktool.utils.capture.MediaProjectionHolder
+
+
+/**
+ * Created by Gallon on 2019/8/7.
+ */
+class PlayNotifyService : Service() {
+    private val TAG = PlayNotifyService::class.java.simpleName
+
+    companion object {
+        private const val PLAY_NOTIFY_ID = 0x1216
+        private const val CHANNEL_ONE_ID = "RECORD_CHANNEL_ONE_ID"
+        private const val CHANNEL_ONE_NAME = "RECORD_CHANNEL_ONE_NAME"
+        const val PLAY_NOTIFY = "record_notify"
+        const val PLAY_NOTIFY_CODE = "record_notify_code"
+        private var playNotifyReceiver: PlayNotifyReceiver = PlayNotifyReceiver()
+    }
+    private var context = Utils.getApp()
+    private var manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+    private var notification: Notification? = null
+
+    inner class PlayNotifyServiceBinder : Binder() {
+        fun getService() = this@PlayNotifyService
+    }
+
+    override fun onBind(intent: Intent?): IBinder? = PlayNotifyServiceBinder()
+
+    override fun onDestroy() {
+        super.onDestroy()
+    }
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        LogUtils.d(TAG, "onStartCommand: $intent")
+        show(null)
+        initBroadcastReceivers()
+        if (intent?.getBooleanExtra("setMediaProject", false) == true) {
+            val resultCode = intent.getIntExtra("code", -1)
+            val data = intent.getParcelableExtra<Intent>("data")
+            val mediaProjectionManager = Utils.getApp().getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
+            MediaProjectionHolder.setMediaProjection(mediaProjectionManager.getMediaProjection(resultCode, data!!))
+        }
+        return super.onStartCommand(intent, flags, startId)
+    }
+
+    fun show(view: RemoteViews?) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val channel = NotificationChannel(CHANNEL_ONE_ID, CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_DEFAULT)
+            channel.enableLights(true) //是否在桌面icon右上角展示小红点
+            channel.lightColor = Color.GREEN //小红点颜色
+            channel.setShowBadge(true) //是否在久按桌面图标时显示此渠道的通知
+            channel.setSound(null, null) //静音 NOTE:首次安装生效 否则需要APP清除数据生效
+            manager.createNotificationChannel(channel)
+        }
+        val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ONE_ID)
+                .setCustomContentView(view)
+                .setWhen(System.currentTimeMillis())
+                .setAutoCancel(false)
+                .setOngoing(true)
+                .setSmallIcon(R.mipmap.ic_launcher)
+                .setOngoing(true)
+        notification = notificationBuilder.build()
+        startForeground(PLAY_NOTIFY_ID, notification)
+    }
+
+    private fun initBroadcastReceivers() {
+        val filter = IntentFilter(PLAY_NOTIFY)
+        context.registerReceiver(playNotifyReceiver, filter)
+    }
+
+    private class PlayNotifyReceiver : BroadcastReceiver() {
+
+        override fun onReceive(context: Context, intent: Intent) {
+            val code = intent.getIntExtra(PLAY_NOTIFY_CODE, -1)
+            if (code == -1) {
+                return
+            }
+
+            LogUtils.d("notification click: $code")
+            if (MediaProjectionHolder.mMediaProjection != null) {
+                SystemClock.sleep(300)
+                GetScreenShotActivity.startCapture()
+            } else {
+                fastStartActivity(context, GetScreenShotActivity::class.java)
+            }
+        }
+    }
+
+}

+ 741 - 0
app/src/main/java/org/yameida/worktool/service/WeworkController.kt

@@ -0,0 +1,741 @@
+package org.yameida.worktool.service
+
+import com.blankj.utilcode.util.LogUtils
+import org.yameida.worktool.Demo
+import org.yameida.worktool.annotation.RequestMapping
+import org.yameida.worktool.model.ExecCallbackBean
+import org.yameida.worktool.model.WeworkMessageBean
+
+/**
+ * 企业微信客服端反转
+ * 被服务端远程调用的服务Controller类
+ */
+object WeworkController {
+
+    lateinit var weworkService: WeworkService
+    var enableLoopRunning = false
+    var mainLoopRunning = false
+
+    /**
+     * 交互通知
+     * @see WeworkMessageBean.TYPE_CONSOLE_TOAST
+     * @param message#errorCode 失败错误码
+     * @param message#errorReason 失败原因
+     */
+    @RequestMapping
+    fun consoleToast(message: ExecCallbackBean): Boolean {
+        LogUtils.d("REQUEST consoleToast(): ${message.messageId} ${message.errorCode} ${message.errorReason}")
+        return WeworkInteractionImpl.consoleToast(message, message.errorCode, message.errorReason)
+    }
+
+    /**
+     * 停止所有任务并返回首页待命
+     * @see WeworkMessageBean.STOP_AND_GO_HOME
+     */
+    @RequestMapping
+    fun stopAndGoHome() {
+        LogUtils.d("REQUEST stopAndGoHome()")
+        enableLoopRunning = false
+        mainLoopRunning = false
+        goHome()
+    }
+
+    /**
+     * 回到首页等待接收新消息
+     * @see WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
+     */
+    @RequestMapping
+    fun loopReceiveNewMessage() {
+        LogUtils.d("REQUEST loopReceiveNewMessage() enableLoopRunning: $enableLoopRunning")
+        WeworkLoopImpl.mainLoop()
+    }
+
+    /**
+     * 在房间内发送消息
+     * @see WeworkMessageBean.SEND_MESSAGE
+     * @param message#titleList 房间名称
+     * @param message#receivedContent 回复内容
+     * @param message#at 要at的昵称
+     * @see WeworkMessageBean.TEXT_TYPE
+     */
+    @RequestMapping
+    fun sendMessage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST sendMessage(): ${message.messageId} ${message.titleList} ${message.receivedContent} ${message.at} ${message.atList?.joinToString()}")
+        return WeworkOperationImpl.sendMessage(
+            message,
+            message.titleList,
+            message.receivedContent,
+            message.at,
+            message.atList
+        )
+    }
+
+    /**
+     * 在房间内指定回复消息
+     * @see WeworkMessageBean.REPLY_MESSAGE
+     * @param message#titleList 房间名称
+     * @param message#receivedName 原始消息的发送者姓名
+     * @param message#originalContent 原始消息的内容
+     * @param message#textType 原始消息的消息类型
+     * @param message#receivedContent 回复内容
+     * @see WeworkMessageBean.TEXT_TYPE
+     */
+    @RequestMapping
+    fun replyMessage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST replyMessage(): ${message.messageId} ${message.receivedName} ${message.originalContent} ${message.textType} ${message.receivedContent}")
+        return WeworkOperationImpl.replyMessage(
+            message,
+            message.titleList,
+            message.receivedName,
+            message.originalContent,
+            message.textType,
+            message.receivedContent
+        )
+    }
+
+    /**
+     * 在房间内转发消息
+     * @see WeworkMessageBean.RELAY_MESSAGE
+     * @param message#titleList 房间名称
+     * @param message#receivedName 原始消息的发送者姓名
+     * @param message#originalContent 原始消息的内容
+     * @param message#textType 原始消息的消息类型
+     * @param message#nameList 待转发姓名列表
+     * @param message#extraText 附加留言 选填
+     * @see WeworkMessageBean.TEXT_TYPE
+     */
+    @RequestMapping
+    fun relayMessage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST relayMessage(): ${message.messageId} ${message.titleList} ${message.receivedName} ${message.originalContent} ${message.textType} ${message.nameList} ${message.extraText}")
+        return WeworkOperationImpl.relayMessage(
+            message,
+            message.titleList,
+            message.receivedName,
+            message.originalContent,
+            message.textType,
+            message.nameList,
+            message.extraText
+        )
+    }
+
+    /**
+     * 初始化群设置
+     * 群名称、群公告、拉人、踢人、群备注、群模板
+     * @see WeworkMessageBean.INIT_GROUP
+     * @param message#groupName 修改群名称
+     * @param message#selectList 添加群成员名称列表 选填
+     * @param message#groupAnnouncement 修改群公告 选填
+     * @param message#groupRemark 修改群备注 选填
+     * @param message#groupTemplate 修改群模板 选填
+     */
+    @RequestMapping
+    fun initGroup(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST initGroup(): ${message.messageId} ${message.groupName} ${message.selectList} ${message.groupAnnouncement} ${message.groupRemark} ${message.groupTemplate}")
+        return WeworkOperationImpl.initGroup(
+            message,
+            message.groupName,
+            message.selectList,
+            message.groupAnnouncement,
+            message.groupRemark,
+            message.groupTemplate
+        )
+    }
+
+    @RequestMapping
+    fun transferGroup(message: WeworkMessageBean): Boolean {
+        return WeworkOperationImpl.transferGroup(
+            message,
+            message.groupName
+        )
+    }
+
+    @RequestMapping
+    fun administrators(message: WeworkMessageBean): Boolean {
+        return WeworkOperationImpl.administrators(
+            message,
+            message.groupName
+        )
+    }
+
+    @RequestMapping
+    fun removeAdministrators(message: WeworkMessageBean): Boolean {
+        return WeworkOperationImpl.removeAdministrators(
+            message,
+            message.groupName
+        )
+    }
+
+    /**
+     * 机器人接口测试
+     * @see WeworkMessageBean.ROBOT_CONTROLLER_TEST
+     */
+    @RequestMapping
+    fun test(message: WeworkMessageBean? = null) {
+        LogUtils.d(message)
+        Demo.test(true)
+    }
+
+    /**
+     * 进入群聊并修改群配置
+     * 群名称、群公告、拉人、踢人、群备注、群模板
+     * @see WeworkMessageBean.UPDATE_GROUP
+     * @param message#groupName 待修改的群
+     * @param message#newGroupName 修改群名 选填
+     * @param message#newGroupAnnouncement 修改群公告 选填
+     * @param message#groupRemark 修改群备注 选填
+     * @param message#groupTemplate 修改群模板 选填
+     * @param message#selectList 添加群成员名称列表/拉人 选填
+     * @param message#showMessageHistory 拉人是否附带历史记录 选填
+     * @param message#removeList 移除群成员名称列表/踢人 选填
+     */
+    @RequestMapping
+    fun updateGroup(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST updateGroup(): ${message.messageId} ${message.groupName} ${message.newGroupName} ${message.newGroupAnnouncement} ${message.selectList} ${message.showMessageHistory} ${message.removeList} ${message.groupRemark} ${message.groupTemplate}")
+        return WeworkOperationImpl.updateGroup(
+            message,
+            message.groupName,
+            message.newGroupName,
+            message.newGroupAnnouncement,
+            message.groupRemark,
+            message.groupTemplate,
+            message.selectList,
+            message.showMessageHistory,
+            message.removeList,
+            message.stopGroupName
+        )
+    }
+
+    /**
+     * 解散群聊
+     * @see WeworkMessageBean.DISMISS_GROUP
+     * @param message#groupName 待解散的群
+     */
+    @RequestMapping
+    fun dismissGroup(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST dismissGroup(): ${message.messageId} ${message.groupName}")
+        return WeworkOperationImpl.dismissGroup(
+            message,
+            message.groupName
+        )
+    }
+
+    /**
+     * 从外部群添加好友
+     * @see WeworkMessageBean.ADD_FRIEND_BY_GROUP
+     * @param message#groupName 外部群
+     * @param message#friend 待添加用户
+     */
+    @RequestMapping
+    fun addFriendByGroup(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST addFriendByGroup(): ${message.messageId} ${message.groupName} ${message.friend}")
+        return WeworkOperationImpl.addFriendByGroup(
+            message,
+            message.groupName,
+            message.friend
+        )
+    }
+
+    /**
+     * 给群成员添加备注
+     * @see WeworkMessageBean.MODIFY_GROUP_MEMBER_INFO
+     * @param message#groupName 外部群
+     * @param message#friend 待添加用户
+     */
+    @RequestMapping
+    fun modifyGroupMemberInfo(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST modifyGroupMemberInfo(): ${message.messageId} ${message.groupName} ${message.friend}")
+        return WeworkOperationImpl.modifyGroupMemberInfo(
+            message,
+            message.groupName,
+            message.friend
+        )
+    }
+
+    /**
+     * 添加待办
+     * @see WeworkMessageBean.ADD_NEED_DEAL
+     * @param message#titleList 内部用户昵称列表
+     * @param message#receivedContent 回复内容
+     */
+    @RequestMapping
+    fun addNeedDeal(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST addNeedDeal(): ${message.messageId} ${message.titleList} ${message.receivedContent}")
+        return WeworkOperationImpl.addNeedDeal(
+            message,
+            message.titleList,
+            message.receivedContent
+        )
+    }
+
+    /**
+     * 打卡
+     * @see WeworkMessageBean.CLOCK_IN
+     */
+    @RequestMapping
+    fun clockIn(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST clockIn(): ${message.messageId}")
+        return WeworkOperationImpl.clockIn(message)
+    }
+
+    /**
+     * 切换企业
+     * @see WeworkMessageBean.SWITCH_CORP
+     * @param message#objectName 企业名称
+     */
+    @RequestMapping
+    fun switchCorp(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST switchCorp(): ${message.messageId} ${message.objectName}")
+        return WeworkOperationImpl.switchCorp(message, message.objectName)
+    }
+
+    /**
+     * 推送微盘图片
+     * @see WeworkMessageBean.PUSH_MICRO_DISK_IMAGE
+     * @param message#titleList 待发送姓名列表
+     * @param message#objectName 图片名称
+     * @param message#extraText 附加留言 可选
+     */
+    @RequestMapping
+    fun pushMicroDiskImage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST pushMicroDiskImage(): ${message.messageId} ${message.titleList} ${message.objectName} ${message.extraText}")
+        return WeworkOperationImpl.pushMicroDiskImage(
+            message,
+            message.titleList,
+            message.objectName,
+            message.extraText
+        )
+    }
+
+    /**
+     * 推送微盘文件
+     * @see WeworkMessageBean.PUSH_MICRO_DISK_FILE
+     * @param message#titleList 待发送姓名列表
+     * @param message#objectName 文件名称
+     * @param message#extraText 附加留言 可选
+     */
+    @RequestMapping
+    fun pushMicroDiskFile(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST pushMicroDiskFile(): ${message.messageId} ${message.titleList} ${message.objectName} ${message.extraText}")
+        return WeworkOperationImpl.pushMicroDiskFile(
+            message,
+            message.titleList,
+            message.objectName,
+            message.extraText
+        )
+    }
+
+    /**
+     * 推送小程序
+     * @see WeworkMessageBean.PUSH_MICROPROGRAM
+     * @param message#titleList 待发送姓名列表
+     * @param message#objectName 小程序名称
+     * @param message#receivedContent 小程序描述
+     * @param message#originalContent 小程序链接地址
+     * @param message#fileUrl 图片地址
+     * @param message#extraText 附加留言 可选
+    不信谣不传谣不造谣     */
+    @RequestMapping
+    fun pushMicroprogram(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST pushMicroprogram(): ${message.messageId} ${message.titleList} ${message.objectName} ${message.receivedContent} ${message.originalContent} ${message.fileUrl} ${message.extraText}")
+        return WeworkOperationImpl.pushMicroprogram(
+            message,
+            message.titleList,
+            message.appId,
+            message.agentId,
+            message.objectName,
+            message.imageUrl,
+            message.username,
+            message.path,
+            message.schema,
+            message.originalContent,
+            message.extraText,
+            message.maxRetryCount
+        )
+    }
+
+    /**
+     * 推送图片
+     * @see WeworkMessageBean.PUSH_MICROPROGRAM
+     * @param message#titleList 待发送姓名列表
+     * @param message#objectName 小程序名称
+     * @param message#receivedContent 小程序描述
+     * @param message#originalContent 小程序链接地址
+     * @param message#fileUrl 图片地址
+     * @param message#extraText 附加留言 可选     */
+    @RequestMapping
+    fun pushPicture(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST pushMicroprogram(): ${message.messageId} ${message.titleList} ${message.objectName} ${message.receivedContent} ${message.originalContent} ${message.fileUrl} ${message.extraText}")
+        return WeworkOperationImpl.pushPicture(
+            message,
+            message.titleList,
+            message.fileUrl,
+            message.appId,
+            message.agentId,
+            message.fileName,
+            message.extraText,
+            message.maxRetryCount
+        )
+    }
+
+
+    /**
+     * 推送腾讯文档
+     * @see WeworkMessageBean.PUSH_OFFICE
+     * TODO 自己的文档分享时可选择权限级别
+     * @param message#titleList 待发送姓名列表
+     * @param message#objectName 腾讯文档名称
+     * @param message#extraText 附加留言 可选
+     */
+    @RequestMapping
+    fun pushOffice(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST pushOffice(): ${message.messageId} ${message.titleList} ${message.objectName} ${message.extraText}")
+        return WeworkOperationImpl.pushOffice(
+            message,
+            message.titleList,
+            message.objectName,
+            message.extraText
+        )
+    }
+
+    /**
+     * 推送文件(网络图片视频和文件等)
+     * @see WeworkMessageBean.PUSH_FILE
+     * @param message#titleList 待发送姓名列表
+     * @param message#objectName 文件名称
+     * @param message#fileUrl 文件网络地址
+     * @param message#fileBase64 文件Base64
+     * @param message#fileType 文件类型
+     * @param message#extraText 附加留言 可选
+     */
+    @RequestMapping
+    fun pushFile(message: WeworkMessageBean): Boolean {
+//        LogUtils.d(
+//            "REQUEST pushFile(): ${message.messageId} ${message.titleList} ${message.objectName} ${message.fileUrl} ${
+//                message.fileBase64?.substring(
+//                    0,
+//                    100
+//                )
+//            } ${message.fileType} ${message.extraText}"
+//        )
+        return WeworkOperationImpl.pushFile(
+            message,
+            message.titleList,
+            message.objectName,
+            message.fileUrl,
+            message.fileBase64,
+            message.fileType,
+            message.extraText,
+            message.maxRetryCount
+        )
+    }
+
+    /**
+     * 推送链接
+     * @see WeworkMessageBean.PUSH_LINK
+     * @param message#titleList 待发送姓名列表
+     * @param message#objectName 文章标题
+     * @param message#receivedContent 文章副标题
+     * @param message#originalContent 文章链接地址
+     * @param message#fileUrl 图片地址
+     * @param message#extraText 附加留言 可选
+     */
+    @RequestMapping
+    fun pushLink(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST pushLink(): ${message.messageId} ${message.titleList} ${message.objectName} ${message.receivedContent} ${message.originalContent} ${message.fileUrl} ${message.extraText}")
+        return WeworkOperationImpl.pushLink(
+            message,
+            message.titleList,
+            message.objectName,
+            message.receivedContent,
+            message.originalContent,
+            message.fileUrl,
+            message.appId,
+            message.agentId,
+            message.extraText,
+            message.maxRetryCount
+        )
+    }
+
+    /**
+     * 撤回消息
+     * @see WeworkMessageBean.RECALL_MESSAGE
+     * @param message#titleList 房间名称
+     * @param message#originalContent 原始消息的内容
+     * @param message#textType 原始消息的消息类型
+     * @see WeworkMessageBean.TEXT_TYPE
+     */
+    @RequestMapping
+    fun recallMessage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST recallMessage(): ${message.messageId} ${message.titleList} ${message.originalContent} ${message.textType}")
+        return WeworkOperationImpl.recallMessage(
+            message,
+            message.titleList,
+            message.originalContent,
+            message.textType
+        )
+    }
+
+    /**
+     * 批量转发
+     * @see WeworkMessageBean.RELAY_MULTI_MESSAGE
+     * @param message#titleList 房间名称
+     * @param message#messageList 消息列表
+     * @param message#nameList 待转发姓名列表
+     * @param message#extraText 附加留言 选填
+     * @see WeworkMessageBean.TEXT_TYPE
+     */
+    @RequestMapping
+    fun relayMultiMessage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST relayMultiMessage(): ${message.messageId} ${message.titleList} ${message.messageList} ${message.nameList} ${message.extraText}")
+        return WeworkOperationImpl.relayMultiMessage(
+            message,
+            message.titleList,
+            message.messageList,
+            message.nameList,
+            message.extraText
+        )
+    }
+
+    /**
+     * 合并转发
+     * @see WeworkMessageBean.RELAY_MERGE_MESSAGE
+     * @param message#titleList 房间名称
+     * @param message#messageList 消息列表
+     * @param message#nameList 待转发姓名列表
+     * @param message#extraText 附加留言 选填
+     * @see WeworkMessageBean.TEXT_TYPE
+     */
+    @RequestMapping
+    fun relayMergeMessage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST relayMergeMessage(): ${message.messageId} ${message.titleList} ${message.messageList} ${message.nameList} ${message.extraText}")
+        return WeworkOperationImpl.relayMergeMessage(
+            message,
+            message.titleList,
+            message.messageList,
+            message.nameList,
+            message.extraText
+        )
+    }
+
+    /**
+     * 批量发送
+     * @see WeworkMessageBean.SEND_MULTI_MESSAGE
+     * @param message#weworkMessageList 消息列表
+     * @param message#nameList 待转发姓名列表
+     * @param message#extraText 附加留言 选填
+     */
+    @RequestMapping
+    fun sendMultiMessage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST sendMultiMessage(): ${message.messageId} ${message.weworkMessageList} ${message.nameList} ${message.extraText}")
+        return WeworkOperationImpl.sendMultiMessage(
+            message,
+            message.weworkMessageList,
+            message.nameList,
+            message.extraText
+        )
+    }
+
+    /**
+     * 设置防骚扰规则
+     * @see WeworkMessageBean.GET_CORP_LIST
+     */
+    @RequestMapping
+    fun setHarassRules(message: WeworkMessageBean): Boolean {
+        return WeworkOperationImpl.setHarassRules(message)
+    }
+
+    /**
+     * 删除防骚扰规则
+     * @see WeworkMessageBean.GET_CORP_LIST
+     */
+    @RequestMapping
+    fun deleteHarassRules(message: WeworkMessageBean): Boolean {
+        return WeworkOperationImpl.deleteHarassRules(message)
+    }
+
+    /**
+     * 取消应用防骚扰规则
+     * @see WeworkMessageBean.GET_CORP_LIST
+     */
+    @RequestMapping
+    fun cancelHarassRules(message: WeworkMessageBean): Boolean {
+        return WeworkOperationImpl.cancelHarassRules(message)
+    }
+
+    /**
+     * 取消应用防骚扰规则
+     * @see WeworkMessageBean.GET_CORP_LIST
+     */
+    @RequestMapping
+    fun applyHarassRules(message: WeworkMessageBean): Boolean {
+        return WeworkOperationImpl.applyHarassRules(message)
+    }
+
+    /**
+     * 合并发送
+     * @see WeworkMessageBean.SEND_MERGE_MESSAGE
+     * @param message#weworkMessageList 消息列表
+     * @param message#nameList 待转发姓名列表
+     * @param message#extraText 附加留言 选填
+     */
+    @RequestMapping
+    fun sendMergeMessage(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST sendMergeMessage(): ${message.messageId} ${message.weworkMessageList} ${message.nameList} ${message.extraText}")
+        return WeworkOperationImpl.sendMergeMessage(
+            message,
+            message.weworkMessageList,
+            message.nameList,
+            message.extraText
+        )
+    }
+
+    /**
+     * 按手机号添加好友
+     * @see WeworkMessageBean.ADD_FRIEND_BY_PHONE
+     * @param message#friend 待添加用户
+     */
+    @RequestMapping
+    fun addFriendByPhone(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST addFriendByPhone(): ${message.messageId} ${message.friend}")
+        return WeworkOperationImpl.addFriendByPhone(message, message.friend)
+    }
+
+    /**
+     * 展示群信息
+     * @see WeworkMessageBean.SHOW_GROUP_INFO
+     * @param message#titleList 待查询群名
+     * @param message#receivedName 原始消息的发送者姓名
+     * @param message#originalContent 原始消息的内容
+     * @param message#textType 原始消息的消息类型
+     */
+    @RequestMapping
+    fun showGroupInfo(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST showGroupInfo(): ${message.messageId} ${message.titleList} ${message.receivedName} ${message.originalContent} ${message.textType}")
+        return WeworkOperationImpl.showGroupInfo(
+            message,
+            message.titleList,
+            message.receivedName,
+            message.originalContent,
+            message.textType
+        )
+    }
+
+    /**
+     * 扫一扫
+     * @see WeworkMessageBean.SCAN_QR_CODE
+     * @param message#fileUrl 图片地址
+     * @param message#fileBase64 文件Base64
+     */
+    @RequestMapping
+    fun scanQrCode(message: WeworkMessageBean): Boolean {
+        LogUtils.d(
+            "REQUEST scanQrCode(): ${message.messageId} ${message.fileUrl} ${
+                message.fileBase64?.substring(
+                    0,
+                    100
+                )
+            }"
+        )
+        return WeworkOperationImpl.scanQrCode(
+            message,
+            message.fileUrl,
+            message.fileBase64
+        )
+    }
+
+    /**
+     * 删除联系人
+     * @see WeworkMessageBean.DELETE_CONTACT
+     * @param message#friend 待删除用户
+     */
+    @RequestMapping
+    fun deleteContact(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST deleteContact(): ${message.messageId} ${message.friend}")
+        return WeworkOperationImpl.deleteContact(message, message.friend)
+    }
+
+    /**
+     * 获取群信息
+     * @see WeworkMessageBean.GET_GROUP_INFO
+     * @param message#selectList 群名列表 为空时去群管理页查询并返回群聊页
+     */
+    @RequestMapping
+    fun getGroupInfo(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST getGroupInfo(): ${message.messageId} ${message.selectList}")
+        return WeworkGetImpl.getGroupInfo(message, message.selectList)
+    }
+
+    /**
+     * 获取好友信息
+     * @see WeworkMessageBean.GET_FRIEND_INFO
+     * @param message#selectList 好友名列表
+     */
+    @RequestMapping
+    fun getFriendInfo(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST getFriendInfo(): ${message.messageId} ${message.selectList}")
+        return WeworkGetImpl.getFriendInfo(message, message.selectList)
+    }
+
+    /**
+     * 获取全部好友信息
+     * @see WeworkMessageBean.GET_ALL_FRIEND_INFO
+     */
+    @RequestMapping
+    fun getAllFriendInfo(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST getAllFriendInfo(): ${message.messageId}")
+        return WeworkGetImpl.getAllFriendInfo(message)
+    }
+
+    /**
+     * 获取全部群信息
+     * @see WeworkMessageBean.GET_ALL_GROUP_INFO
+     */
+    @RequestMapping
+    fun getAllGroupInfo(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST getAllGroupInfo(): ${message.messageId}")
+        return WeworkGetImpl.getAllGroupInfo(message)
+    }
+
+    /**
+     * 获取本地文件
+     * @see WeworkMessageBean.GET_LOCAL_FILE
+     * @param message#fileUrl 文件地址
+     */
+    @RequestMapping
+    fun getLocalFile(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST getLocalFile(): ${message.messageId} ${message.fileUrl}")
+        return WeworkGetImpl.getLocalFile(message, message.fileUrl)
+    }
+
+    /**
+     * 获取我的信息
+     * @see WeworkMessageBean.GET_MY_INFO
+     */
+    @RequestMapping
+    fun getMyInfo(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST getMyInfo(): ${message.messageId}")
+        return WeworkGetImpl.getMyInfo(message)
+    }
+
+    /**
+     * 获取最近聊天列表
+     * @see WeworkMessageBean.GET_RECENT_LIST
+     */
+    @RequestMapping
+    fun getRecentList(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST getRecentList(): ${message.messageId}")
+        return WeworkGetImpl.getRecentList(message)
+    }
+
+    /**
+     * 获取企业列表
+     * @see WeworkMessageBean.GET_CORP_LIST
+     */
+    @RequestMapping
+    fun getCorpList(message: WeworkMessageBean): Boolean {
+        LogUtils.d("REQUEST getCorpList(): ${message.messageId}")
+        return WeworkGetImpl.getCorpList(message)
+    }
+
+
+}

+ 554 - 0
app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt

@@ -0,0 +1,554 @@
+package org.yameida.worktool.service
+
+import com.blankj.utilcode.util.FileUtils
+import com.blankj.utilcode.util.GsonUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.SPUtils
+import org.yameida.worktool.Constant
+import org.yameida.worktool.model.ExecCallbackBean
+import org.yameida.worktool.model.WeworkMessageBean
+import org.yameida.worktool.utils.*
+import java.io.File
+
+/**
+ * 获取数据类型 500 实现类
+ */
+object WeworkGetImpl {
+
+    /**
+     * 获取群信息
+     * @see WeworkMessageBean.GET_GROUP_INFO
+     * @param selectList 群名列表 为空时去群管理页查询并返回群聊页
+     */
+    fun getGroupInfo(message: WeworkMessageBean, selectList: List<String>): Boolean {
+        for (groupName in selectList) {
+            if (WeworkRoomUtil.intoRoom(groupName) && WeworkRoomUtil.intoGroupManager()) {
+                val groupInfo = getGroupInfoDetail()
+                WeworkController.weworkService.webSocketManager.send(groupInfo)
+            }
+        }
+        return true
+    }
+
+    /**
+     * 获取好友信息
+     * @see WeworkMessageBean.GET_FRIEND_INFO
+     * @param selectList 好友名列表
+     */
+    fun getFriendInfo(message: WeworkMessageBean, selectList: List<String>): Boolean {
+        return true
+    }
+
+    /**
+     * 获取全部好友信息
+     * @see WeworkMessageBean.GET_ALL_FRIEND_INFO
+     */
+    fun getAllFriendInfo(message: WeworkMessageBean): Boolean {
+        val startTime = System.currentTimeMillis()
+        goHomeTab("通讯录")
+        sleep(Constant.CHANGE_PAGE_INTERVAL)
+        AccessibilityUtil.scrollToTop(WeworkController.weworkService, getRoot())
+        val customerFlag = AccessibilityUtil.findAllByText(getRoot(), "我的客户", exact = true).lastOrNull()
+        if (customerFlag != null) {
+            AccessibilityUtil.performClick(customerFlag)
+            AccessibilityExtraUtil.loadingPage("BaseContentActivity")
+            val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView, Views.RecyclerView)
+            if (list == null) {
+                LogUtils.e("未找到我的客户列表")
+                uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到我的客户列表", startTime)
+                return false
+            }
+            val set = linkedSetOf<String>()
+            val onScrollListener = object : AccessibilityUtil.OnScrollListener() {
+                override fun onScroll(): Boolean {
+                    if (!AccessibilityExtraUtil.loadingPage("BaseContentActivity")) {
+                        return true
+                    }
+                    list.refresh()
+                    for (i in 0 until list.childCount) {
+                        val item = list.getChild(i)
+                        if (item.className != Views.RelativeLayout) {
+                            continue
+                        }
+                        val name = AccessibilityUtil.findOneByClazz(item, Views.TextView)?.text?.toString()
+                            ?: continue
+                        if (!name.matches("(标签)|(其他外部联系人)|((共?.*个)?客户)".toRegex())) {
+                            set.add(name)
+                        }
+                    }
+                    return false
+                }
+            }
+            //滚动前先获取一次
+            onScrollListener.onScroll()
+            AccessibilityUtil.scrollToBottom(WeworkController.weworkService, list, listener = onScrollListener, maxRetry = 100)
+            LogUtils.d("客户数量: ${set.size} 客户列表: ${set.joinToString()}")
+            message.nameList = set.toList()
+            uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime)
+            return true
+        } else {
+            LogUtils.e("未找到我的客户入口")
+            uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到我的客户入口", startTime)
+            return false
+        }
+    }
+
+    /**
+     * 获取全部群信息
+     * @see WeworkMessageBean.GET_ALL_GROUP_INFO
+     */
+    fun getAllGroupInfo(message: WeworkMessageBean): Boolean {
+        val startTime = System.currentTimeMillis()
+        goHomeTab("通讯录")
+        sleep(Constant.CHANGE_PAGE_INTERVAL)
+        AccessibilityUtil.scrollToTop(WeworkController.weworkService, getRoot())
+        if (AccessibilityUtil.findTextAndClick(getRoot(), "群聊", exact = true)) {
+            AccessibilityExtraUtil.loadingPage("GroupSavedListActivity")
+            val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView, Views.RecyclerView)
+            if (list == null) {
+                LogUtils.e("未找到群聊列表")
+                uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到群聊列表", startTime)
+                return false
+            }
+            val set = linkedSetOf<String>()
+            val onScrollListener = object : AccessibilityUtil.OnScrollListener() {
+                override fun onScroll(): Boolean {
+                    if (!AccessibilityExtraUtil.loadingPage("GroupSavedListActivity")) {
+                        return true
+                    }
+                    list.refresh()
+                    AccessibilityUtil.findAllOnceByClazz(list, Views.TextView).map {
+                        val groupName = it.text?.toString()
+                        if (groupName != null && !groupName.matches("(.*个)?群聊".toRegex())) {
+                            set.add(groupName)
+                        }
+                        false
+                    }
+                    return false
+                }
+            }
+            //滚动前先获取一次
+            onScrollListener.onScroll()
+            AccessibilityUtil.scrollToBottom(WeworkController.weworkService, list, listener = onScrollListener, maxRetry = 100)
+            LogUtils.d("群数量: ${set.size} 群列表: ${set.joinToString()}")
+            for (groupName in set) {
+                if (WeworkRoomUtil.intoRoom(groupName) && WeworkRoomUtil.intoGroupManager()) {
+                    val groupInfo = getGroupInfoDetail(saveAddress = false, saveMembers = true)
+                    WeworkController.weworkService.webSocketManager.send(groupInfo)
+                }
+            }
+            uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime)
+            return true
+        } else {
+            LogUtils.e("未找到群聊入口")
+            uploadCommandResult(message, ExecCallbackBean.ERROR_BUTTON, "未找到群聊入口", startTime)
+            return false
+        }
+    }
+
+    /**
+     * 获取本地文件
+     * @see WeworkMessageBean.GET_LOCAL_FILE
+     * @param fileUrl 文件地址
+     */
+    fun getLocalFile(message: WeworkMessageBean, fileUrl: String?): Boolean {
+        val startTime = System.currentTimeMillis()
+        val fileUrl = if (fileUrl.isNullOrBlank()) {
+            val currentLogFilePath = LogUtils.getCurrentLogFilePath()
+            FileUtils.copy(currentLogFilePath, "$currentLogFilePath.snapshot")
+            "$currentLogFilePath.snapshot"
+        } else fileUrl
+        LogUtils.d("localFileUrl: $fileUrl")
+        val file = File(fileUrl)
+        if (!file.exists()) {
+            LogUtils.e("文件不存在: ${file.absolutePath}")
+            uploadCommandResult(message, ExecCallbackBean.ERROR_TARGET, "文件不存在: ${file.absolutePath}", startTime)
+            return false
+        }
+        HttpUtil.pushLocalFile(file)
+        LogUtils.d("推送本地文件成功: ${file.absolutePath}")
+        uploadCommandResult(message, ExecCallbackBean.SUCCESS, "", startTime)
+        return true
+    }
+
+    /**
+     * 获取我的信息
+     */
+    fun getMyInfo(message: WeworkMessageBean): Boolean {
+        if (!goHomeTab("我")) {
+            LogUtils.d("未找到我的信息")
+            log("未找到我的信息")
+            goHomeTab("消息")
+            val firstTv = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView)
+                .firstOrNull { it.text == null }
+            AccessibilityUtil.performClick(firstTv, retry = false)
+            sleep(Constant.CHANGE_PAGE_INTERVAL)
+            val listviewList = AccessibilityUtil.findAllOnceByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup)
+                .filter { it.childCount >= 2 }
+            val list = listviewList.firstOrNull()
+            if (list != null) {
+                val corpList = arrayListOf<String>()
+                for (i in 0 until list.childCount) {
+                    val item = list.getChild(i)
+                    val tvList = AccessibilityUtil.findAllOnceByClazz(item, Views.TextView)
+                    val textList = tvList.mapNotNull { it.text?.toString() }
+                    if (textList.isNotEmpty()) {
+                        corpList.add(textList[0])
+                    }
+                }
+                LogUtils.d("我的企业", GsonUtils.toJson(corpList))
+                val weworkMessageBean = WeworkMessageBean()
+                weworkMessageBean.type = WeworkMessageBean.GET_CORP_LIST
+                weworkMessageBean.titleList = corpList
+                WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
+            } else {
+                LogUtils.e("未找到企业列表")
+            }
+            val newFirstTv = AccessibilityUtil.findOneByClazz(getRoot(), Views.TextView)
+            val nickname = newFirstTv?.text?.toString()
+            if (nickname != null) {
+                LogUtils.d("找到我的昵称")
+                log("找到我的昵称")
+                var corp: String? = null
+                val info = StringBuilder()
+                if (AccessibilityUtil.performClick(newFirstTv)) {
+                    sleep(Constant.CHANGE_PAGE_INTERVAL)
+                    val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup, minChildCount = 2)
+                    if (list != null) {
+                        val myInfoLayout = list.getChild(0)
+                        val tvList = AccessibilityUtil.findAllByClazz(myInfoLayout, Views.TextView)
+                            .filter { it.text != null }
+                        if (tvList.isNotEmpty()) {
+                            corp = tvList[0].text.toString()
+                            tvList.forEach { info.append(it.text).append("-") }
+                            info.setLength(info.length - 1)
+                            LogUtils.v("corp", corp)
+                            LogUtils.v("info", info.toString())
+                        }
+                        if (tvList.size > 1) {
+                            if (!SPUtils.getInstance("myInfo").getBoolean("realName", true)) {
+                                if (tvList[1].text?.toString() == "未认证" && tvList.size > 2) {
+                                    log("企业未认证: $info")
+                                    AccessibilityUtil.performClick(tvList[2])
+                                } else {
+                                    AccessibilityUtil.performClick(tvList[1])
+                                }
+                                getRealName(nickname)
+                            } else {
+                                LogUtils.d("已实名认证")
+                            }
+                        }
+                    }
+                }
+                Constant.myName = nickname
+                Constant.myCorp = corp ?: ""
+                LogUtils.d("我的昵称: ${Constant.myName}")
+                val weworkMessageBean = WeworkMessageBean()
+                weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO
+                weworkMessageBean.myInfo = WeworkMessageBean.MyInfo().apply {
+                    name = nickname
+                    corporation = corp
+                    sumInfo = info.toString()
+                }
+                WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
+                return true
+            } else {
+                LogUtils.d("未找到我的昵称")
+                log("未找到我的昵称")
+                return false
+            }
+        }
+        AccessibilityUtil.performClick(AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView))
+        sleep(Constant.CHANGE_PAGE_INTERVAL)
+        val relativeLayoutList = AccessibilityUtil.findAllByClazz(getRoot(), Views.RelativeLayout, minSize = 50)
+        val myInfo = WeworkMessageBean.MyInfo()
+        for (relativeLayout in relativeLayoutList.filter { it.childCount >= 2 }) {
+            val textViewList = AccessibilityUtil.findAllOnceByClazz(relativeLayout, Views.TextView)
+            if (textViewList.size >= 2) {
+                val firstText = textViewList[0].text?.toString()
+                if (firstText == "姓名" && myInfo.name == null) {
+                    myInfo.name = textViewList[1].text?.toString() ?: ""
+                    Constant.myName = myInfo.name
+                    LogUtils.d("我的昵称: ${Constant.myName}")
+                }
+                if (firstText == "别名" && myInfo.alias == null) {
+                    myInfo.alias = textViewList[1].text?.toString() ?: ""
+                }
+                if (firstText == "性别" && myInfo.gender == null) {
+                    myInfo.gender = textViewList[1].text?.toString() ?: ""
+                }
+                if (firstText == "对外信息显示" && myInfo.showName == null) {
+                    myInfo.showName = textViewList[1].text?.toString() ?: ""
+                }
+                if (firstText == "工作签名" && myInfo.workSign == null) {
+                    myInfo.workSign = textViewList[1].text?.toString() ?: ""
+                }
+                if (firstText == "所在企业" && myInfo.corporation == null) {
+                    myInfo.corporation = textViewList[1].text?.toString() ?: ""
+                    Constant.myCorp = myInfo.corporation
+                }
+                if (firstText == "手机" && myInfo.phone == null) {
+                    myInfo.phone = textViewList[1].text?.toString() ?: ""
+                }
+                if (firstText == "职务" && myInfo.job == null) {
+                    myInfo.job = textViewList[1].text?.toString() ?: ""
+                }
+            }
+        }
+        if (!SPUtils.getInstance("myInfo").getBoolean("realName", true)) {
+            getRealName(myInfo.name)
+        } else {
+            LogUtils.d("已实名认证")
+        }
+        LogUtils.d("我的信息", GsonUtils.toJson(myInfo))
+        val weworkMessageBean = WeworkMessageBean()
+        weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO
+        weworkMessageBean.myInfo = myInfo
+        WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
+        return getCorpList(message)
+    }
+
+    /**
+     * 获取群名、群主、群成员数、群公告、群备注
+     */
+    fun getGroupInfoDetail(saveAddress: Boolean = true, saveMembers: Boolean = false): WeworkMessageBean {
+        val weworkMessageBean = WeworkMessageBean()
+        weworkMessageBean.type = WeworkMessageBean.GET_GROUP_INFO
+        val tvManagerFlag = AccessibilityUtil.findOneByText(getRoot(), "全部群成员", "微信用户创建", timeout = 2000)
+        if (tvManagerFlag?.text?.toString()?.contains("微信用户创建") == true) {
+            var button = AccessibilityUtil.findFrontNode(tvManagerFlag)
+            if (button?.className == Views.ImageView) {
+                button = AccessibilityUtil.findFrontNode(button)
+            }
+            val tvGroupName = AccessibilityUtil.findOnceByClazz(button, Views.TextView)
+            if (tvGroupName != null && tvGroupName.text != null) {
+                LogUtils.d("群名: " + tvGroupName.text)
+                weworkMessageBean.groupName = tvGroupName.text.toString()
+            }
+        }
+        if (weworkMessageBean.groupName.isNullOrEmpty()) {
+            val groupNameTv = AccessibilityUtil.findOnceByText(getRoot(), "群聊名称", exact = true)
+            if (groupNameTv != null) {
+                val tvList = AccessibilityUtil.findAllOnceByClazz(
+                    groupNameTv.parent?.parent?.parent,
+                    Views.TextView
+                )
+                if (tvList.size >= 2) {
+                    val groupName = tvList[1]
+                    LogUtils.d("群名: " + groupName.text)
+                    weworkMessageBean.groupName = groupName.text.toString()
+                }
+            }
+        }
+        val tvCountFlag = AccessibilityUtil.findOnceByText(getRoot(), "查看全部群成员", exact = true)
+        val tvCount = AccessibilityUtil.findBackNode(tvCountFlag)
+        if (tvCount != null && tvCount.text != null) {
+            LogUtils.d("群成员: " + tvCount.text)
+            val count = tvCount.text.toString().replace("人", "")
+            weworkMessageBean.groupNumber = count.toIntOrNull()
+        }
+        val gridView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.GridView)
+        if (gridView != null && gridView.childCount >= 2) {
+            LogUtils.i("获取群成员 使用GridView")
+            val tvOwnerName = AccessibilityUtil.findOnceByClazz(gridView.getChild(0), Views.TextView)
+            if (tvOwnerName != null && tvOwnerName.text != null) {
+                LogUtils.d("群主: " + tvOwnerName.text)
+                weworkMessageBean.groupOwner = tvOwnerName.text.toString()
+            }
+            if ((weworkMessageBean.groupNumber ?: 0) <= 8) {
+                val set = linkedSetOf<String>()
+                for (i in 0 until gridView.childCount) {
+                    val item = gridView.getChild(i)
+                    val name = AccessibilityUtil.findOnceByClazz(item, Views.TextView)?.text?.toString()
+                        ?: continue
+                    set.add(name)
+                }
+                LogUtils.d("群成员: ${set.joinToString()}")
+                weworkMessageBean.nameList = set.toList()
+            }
+        } else {
+            LogUtils.i("获取群成员 使用RecyclerView")
+            val recyclerViewList = AccessibilityUtil.findAllOnceByClazz(getRoot(), Views.RecyclerView)
+            if (recyclerViewList.size >= 2 && recyclerViewList[1].childCount >= 2) {
+                val rvList = recyclerViewList[1]
+                val tvOwnerName = AccessibilityUtil.findOnceByClazz(rvList.getChild(0), Views.TextView)
+                if (tvOwnerName != null && tvOwnerName.text != null) {
+                    LogUtils.d("群主: " + tvOwnerName.text)
+                    weworkMessageBean.groupOwner = tvOwnerName.text.toString()
+                }
+                if ((weworkMessageBean.groupNumber ?: 0) <= 8) {
+                    val set = linkedSetOf<String>()
+                    for (i in 0 until rvList.childCount) {
+                        val item = rvList.getChild(i)
+                        val name = AccessibilityUtil.findOnceByClazz(item, Views.TextView)?.text?.toString()
+                            ?: continue
+                        set.add(name)
+                    }
+                    LogUtils.d("群成员: ${set.joinToString()}")
+                    weworkMessageBean.nameList = set.toList()
+                }
+            }
+        }
+        val tvAnnouncementFlag = AccessibilityUtil.findOnceByText(getRoot(), "群公告", exact = true)
+        val tvAnnouncement = AccessibilityUtil.findBackNode(tvAnnouncementFlag)
+        if (tvAnnouncement != null && tvAnnouncement.text != null) {
+            LogUtils.d("群公告: " + tvAnnouncement.text)
+            weworkMessageBean.groupAnnouncement = tvAnnouncement.text.toString()
+        }
+        val tvRemarkFlag = AccessibilityUtil.findOnceByText(getRoot(), "备注", exact = true)
+        val tvRemark = AccessibilityUtil.findOnceByClazz(AccessibilityUtil.findBackNode(tvRemarkFlag), Views.TextView)
+        if (tvRemark != null && tvRemark.text != null) {
+            LogUtils.d("群备注: " + tvRemark.text)
+            weworkMessageBean.groupRemark = tvRemark.text.toString()
+        }
+        if (saveMembers && weworkMessageBean.nameList.isNullOrEmpty()) {
+            if (AccessibilityUtil.findTextAndClick(getRoot(), "查看全部群成员", exact = true, timeout = 0)) {
+                val userList = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
+                if (userList != null) {
+                    val set = linkedSetOf<String>()
+                    val onScrollListener = object : AccessibilityUtil.OnScrollListener() {
+                        override fun onScroll(): Boolean {
+                            userList.refresh()
+                            for (i in 0 until userList.childCount) {
+                                val item = userList.getChild(i)
+                                val name = AccessibilityUtil.findOnceByClazz(item, Views.TextView)?.text?.toString()
+                                    ?: continue
+                                set.add(name)
+                            }
+                            return false
+                        }
+                    }
+                    //滚动前先获取一次
+                    onScrollListener.onScroll()
+                    AccessibilityUtil.scrollToBottom(WeworkController.weworkService, getRoot(), listener = onScrollListener, maxRetry = 100)
+                    LogUtils.d("群成员: ${set.joinToString()}")
+                    weworkMessageBean.nameList = set.toList()
+                } else {
+                    LogUtils.e("未找到群成员列表")
+                }
+                backPress()
+            }
+        }
+        if (saveAddress) {
+            val tvAddressFlag = AccessibilityUtil.scrollAndFindByText(WeworkController.weworkService, getRoot(), "保存到通讯录", exact = true)
+            val tvAddress = AccessibilityUtil.findBackNode(tvAddressFlag, minChildCount = 1)
+            val addressDesc = AccessibilityUtil.findOnceByDesc(tvAddress, "false", "true", exact = true)
+            if (addressDesc?.contentDescription == "false") {
+                LogUtils.d("未保存到通讯录 进行保存...")
+                AccessibilityUtil.performClick(addressDesc)
+            }
+        }
+        backPress()
+        return weworkMessageBean
+    }
+
+    /**
+     * 获取最近聊天列表
+     */
+    fun getRecentList(message: WeworkMessageBean): Boolean {
+        goHome()
+        AccessibilityUtil.scrollToTop(WeworkController.weworkService, getRoot())
+        val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup)
+        if (list != null && list.childCount >= 2) {
+            val listBriefList = LinkedHashSet<WeworkMessageBean.SubMessageBean>()
+            val onScrollListener = object : AccessibilityUtil.OnScrollListener() {
+                override fun onScroll(): Boolean {
+                    list.refresh()
+                    for (i in 0 until list.childCount) {
+                        val item = list.getChild(i)
+                        val tempList = arrayListOf<WeworkMessageBean.ItemMessageBean>()
+                        val tvList = AccessibilityUtil.findAllOnceByClazz(item, Views.TextView).mapNotNull { it.text }.filter { !it.startsWith("@") }
+                        tvList.forEach { tempList.add(WeworkMessageBean.ItemMessageBean(null, it.toString())) }
+                        listBriefList.add(WeworkMessageBean.SubMessageBean(null, null, tempList, null))
+                        //tvList title/time/content
+                        if (tvList.size == 3) {
+                            //只查看最近一周内的消息
+                            if (tvList[1].isNotBlank() && !tvList[1].contains("(刚刚)|(分钟前)|(上午)|(下午)|(昨天)|(星期)|(日程)|(会议)|(:)".toRegex())) {
+                                return true
+                            }
+                        }
+                    }
+                    return false
+                }
+            }
+            //滚动前先获取一次
+            onScrollListener.onScroll()
+            AccessibilityUtil.scrollToBottom(WeworkController.weworkService, getRoot(), listener = onScrollListener)
+            LogUtils.d("最近聊天列表", GsonUtils.toJson(listBriefList))
+            val weworkMessageBean = WeworkMessageBean()
+            weworkMessageBean.type = WeworkMessageBean.GET_RECENT_LIST
+            weworkMessageBean.messageList = listBriefList.toList()
+            WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
+        }
+        return true
+    }
+
+    /**
+     * 获取企业列表
+     */
+    fun getCorpList(message: WeworkMessageBean): Boolean {
+        goHomeTab("消息")
+        val firstTv = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView)
+            .firstOrNull { it.text == null }
+        AccessibilityUtil.performClick(firstTv, retry = false)
+        sleep(Constant.CHANGE_PAGE_INTERVAL)
+        val listviewList = AccessibilityUtil.findAllOnceByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup)
+            .filter { it.childCount >= 2 }
+        val list = listviewList.firstOrNull()
+        if (list != null) {
+            val corpList = arrayListOf<String>()
+            for (i in 0 until list.childCount) {
+                val item = list.getChild(i)
+                val tvList = AccessibilityUtil.findAllOnceByClazz(item, Views.TextView)
+                val textList = tvList.mapNotNull { it.text?.toString() }
+                if (textList.isNotEmpty()) {
+                    corpList.add(textList[0])
+                }
+            }
+            LogUtils.d("我的企业", GsonUtils.toJson(corpList))
+            val weworkMessageBean = WeworkMessageBean()
+            weworkMessageBean.type = WeworkMessageBean.GET_CORP_LIST
+            weworkMessageBean.titleList = corpList
+            WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
+            goHome()
+            return true
+        } else {
+            LogUtils.e("未找到企业列表")
+            return false
+        }
+    }
+
+
+    /**
+     * 获取实名状态
+     */
+    private fun getRealName(nickname: String) {
+        if (AccessibilityExtraUtil.loadingPage("SettingMineInfoActivity")) {
+            val external = AccessibilityUtil.findOnceByText(getRoot(), "对外信息", "对外显示")
+            val tvExternal = AccessibilityUtil.findAllOnceByClazz(external?.parent, Views.TextView).firstOrNull { it.text != null && it.text.toString().contains("@") }
+            val split = tvExternal?.text?.toString()?.split("@")
+            if (split != null) {
+                val externalName = split[0]
+                LogUtils.d("对外显示名: $externalName")
+                log("对外显示名: $externalName")
+            }
+            if (AccessibilityUtil.findTextAndClick(getRoot(), "姓名", exact = true)) {
+                val realNameFlag = AccessibilityUtil.findOneByText(getRoot(), "实名认证", exact = true)
+                if (realNameFlag != null) {
+                    val notRealName = AccessibilityUtil.findOnceByText(getRoot(), "未认证")
+                    val realName = AccessibilityUtil.findOnceByClazz(AccessibilityUtil.findBackNode(realNameFlag, 1), Views.TextView)
+                    if (notRealName != null) {
+                        LogUtils.d("未实名认证: $nickname")
+                        error("未实名认证: $nickname")
+                        SPUtils.getInstance("myInfo").put("realName", false)
+                    } else if (realName != null) {
+                        LogUtils.d("实名认证: ${realName.text}")
+                        log("实名认证: ${realName.text}")
+                        SPUtils.getInstance("myInfo").put("realName", true)
+                    }
+                }
+            }
+        }
+    }
+
+}

+ 28 - 0
app/src/main/java/org/yameida/worktool/service/WeworkInteractionImpl.kt

@@ -0,0 +1,28 @@
+package org.yameida.worktool.service
+
+import com.blankj.utilcode.util.LogUtils
+import com.hjq.toast.ToastUtils
+import org.yameida.worktool.model.ExecCallbackBean
+
+
+/**
+ * 消息类型 100
+ */
+object WeworkInteractionImpl {
+
+    /**
+     * 交互通知
+     * @param errorCode 失败错误码
+     * @param errorReason 失败原因
+     */
+    fun consoleToast(
+        message: ExecCallbackBean,
+        errorCode: Int?,
+        errorReason: String?
+    ): Boolean {
+        LogUtils.e("错误提示 错误码: $errorCode 错误信息: $errorReason")
+        ToastUtils.show("错误提示 错误码: $errorCode 错误信息: $errorReason")
+        return true
+    }
+
+}

文件差異過大導致無法顯示
+ 1150 - 0
app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt


文件差異過大導致無法顯示
+ 6409 - 0
app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt


+ 177 - 0
app/src/main/java/org/yameida/worktool/service/WeworkService.kt

@@ -0,0 +1,177 @@
+package org.yameida.worktool.service
+
+import android.accessibilityservice.AccessibilityService
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.FileObserver
+import android.util.Log
+import android.view.accessibility.AccessibilityEvent
+import com.blankj.utilcode.util.AppUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.SPUtils
+import okhttp3.Response
+import okhttp3.WebSocket
+import okhttp3.WebSocketListener
+import org.yameida.worktool.Constant
+import org.yameida.worktool.Demo
+import org.yameida.worktool.observer.MultiFileObserver
+import org.yameida.worktool.utils.WebSocketManager
+import kotlin.concurrent.thread
+
+/**
+ * 企业微信辅助服务
+ * rootInActiveWindow获取的是当前交互界面窗口的根view 需要验证包名
+ * event.source则不需要验证包名获取窗口并可获得事件详情
+ */
+class WeworkService : AccessibilityService() {
+    private val TAG = "WeworkService"
+    lateinit var webSocketManager: WebSocketManager
+    var currentPackage = ""
+    var currentClass = ""
+    var currentClassPackage = ""
+
+    companion object {
+        private var mFileObserver: FileObserver? = null
+    }
+
+    override fun onServiceConnected() {
+        LogUtils.i("初始化成功")
+        //隐藏软键盘模式
+        softKeyboardController.showMode = SHOW_MODE_HIDDEN
+        WeworkController.weworkService = this
+        WeworkController.enableLoopRunning = true
+        //初始化长连接
+        initWebSocket()
+        //初始化消息处理器
+        MyLooper.init()
+        //初始化图片接收
+        initObserver()
+        //开发者可以在这里添加测试代码 启动时调用一次
+        thread { Demo.test(AppUtils.isAppDebug()) }
+
+        //监听是否修改链接号并重新长连接
+        registerReceiver(object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                if (intent.getStringExtra("type") == "modify_channel") {
+                    LogUtils.e("更新channel")
+                    webSocketManager.close(1000, "modify_channel")
+                    initWebSocket()
+                }
+            }
+        }, IntentFilter(Constant.WEWORK_NOTIFY))
+    }
+
+
+    private fun initWebSocket() {
+        val url = Constant.getWsUrl()
+        val listener = EchoWebSocketListener()
+        LogUtils.d("initWebSocket: $url")
+        webSocketManager = WebSocketManager(url, listener)
+    }
+
+    private fun initObserver() {
+        if (!Constant.pushImage) return
+        try {
+            LogUtils.d("initObserver... mFileObserver is null ? ${mFileObserver == null}")
+            if (mFileObserver == null) {
+                mFileObserver =
+                    MultiFileObserver("/storage/emulated/0/Android/data/com.tencent.wework/files/imagecache/imagemsg2");
+                mFileObserver?.startWatching()
+                log("startWatching...")
+            } else {
+                mFileObserver?.stopWatching()
+                mFileObserver?.startWatching()
+                log("restartWatching...")
+            }
+        } catch (e: Exception) {
+            LogUtils.e(e)
+            error("initObserver startWatching error!: ${e.message}")
+        }
+    }
+
+    /**
+     * TYPE_WINDOW_CONTENT_CHANGED 内容变化
+     * TYPE_VIEW_SCROLLED 列表滚动
+     * @param event
+     */
+    override fun onAccessibilityEvent(event: AccessibilityEvent) {
+        currentPackage = event.packageName?.toString() ?: ""
+        val className = event.className?.toString() ?: ""
+        if (className.contains(currentPackage)) {
+            LogUtils.d("更新当前页面: currentPackage: ${event.packageName} className: ${event.className}")
+            currentClass = className
+            currentClassPackage = currentPackage
+        }
+    }
+
+    override fun onInterrupt() {
+        LogUtils.i("onInterrupt")
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        LogUtils.i("onDestroy")
+        //关闭自动回复
+        WeworkController.enableLoopRunning = false
+        //隐藏软键盘模式
+        softKeyboardController.showMode = SHOW_MODE_AUTO
+        webSocketManager.close(1000, "service Destroy")
+    }
+
+    inner class EchoWebSocketListener : WebSocketListener() {
+        private val TAG = "WeworkService.EchoWebSocketListener"
+        private lateinit var socket: WebSocket
+        override fun onOpen(webSocket: WebSocket, response: Response) {
+            socket = webSocket
+            Log.e(TAG, "连接建立")
+            val robotId = Constant.robotId
+            val appVersion = SPUtils.getInstance().getString("appVersion", "")
+            val workVersion = SPUtils.getInstance().getString("workVersion", "")
+            val deviceRooted = SPUtils.getInstance().getBoolean("deviceRooted", false)
+            val hook = SPUtils.getInstance().getBoolean("hook", false)
+            LogUtils.i("连接建立: $robotId appVersion: $appVersion workVersion: $workVersion deviceRooted: $deviceRooted hook: $hook")
+            log("连接建立: $robotId appVersion: $appVersion workVersion: $workVersion deviceRooted: $deviceRooted hook: $hook")
+            LogUtils.i("设置自动跳转企业微信")
+            sendBroadcast(true)
+        }
+
+        override fun onMessage(webSocket: WebSocket, text: String) {
+            LogUtils.i("onMessage: ${ if (text.length > 1000) (text.substring(0, 1000) + "...") else text }")
+            try {
+                MyLooper.onMessage(webSocket, text)
+            } catch (e: Exception) {
+                LogUtils.e(e)
+                error(e.message)
+            }
+        }
+
+        override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
+            super.onClosed(webSocket, code, reason)
+            //服务器关闭后
+            Log.e(TAG, "连接关闭 $reason")
+            sendBroadcast(false)
+        }
+
+        override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
+            super.onClosing(webSocket, code, reason)
+            socket.close(code, reason)
+            Log.e(TAG, "服务端关闭连接 $code: $reason")
+            sendBroadcast(false)
+        }
+
+        override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
+            //服务器中断
+            Log.e(TAG, "连接错误: " + t.toString() + response.toString())
+            sendBroadcast(false)
+        }
+
+        private fun sendBroadcast(switch: Boolean) {
+            sendBroadcast(Intent(Constant.WEWORK_NOTIFY).apply {
+                putExtra("type", "openWs")
+                putExtra("switch", switch)
+            })
+        }
+    }
+}

+ 43 - 0
app/src/main/java/org/yameida/worktool/utils/AccessibilityExtraUtil.kt

@@ -0,0 +1,43 @@
+package org.yameida.worktool.utils
+
+import android.util.Log
+import org.yameida.worktool.service.WeworkController
+import java.lang.Thread.sleep
+
+
+/**
+ * 无障碍服务扩展类
+ * 注意:操作均为阻塞式,原则上本工具类所有操作都应在子线程执行
+ */
+object AccessibilityExtraUtil {
+    private const val tag = "AccessibilityExtraUtil"
+    private const val SHORT_INTERVAL = 150L
+    private const val SCROLL_INTERVAL_NATIVE = 500L
+    private const val SCROLL_INTERVAL = 800L
+
+    /**
+     * 等待进入页面
+     * @param clazzList 页面Class
+     * @param timeout 检查超时时间
+     */
+    fun loadingPage(
+        vararg clazzList: String,
+        timeout: Long = 5000
+    ): Boolean {
+        val service = WeworkController.weworkService
+        val startTime = System.currentTimeMillis()
+        var currentTime = startTime
+        while (currentTime - startTime <= timeout) {
+            if (service.currentClass in clazzList || service.currentClass.split(".").last() in clazzList) {
+                Log.v(tag, "loadingPage: ${clazzList.joinToString()}")
+                sleep(SCROLL_INTERVAL)
+                return true
+            }
+            sleep(SHORT_INTERVAL)
+            currentTime = System.currentTimeMillis()
+        }
+        Log.e(tag, "loadingPage: not found: ${clazzList.joinToString()} current: ${service.currentClass}")
+        return false
+    }
+
+}

文件差異過大導致無法顯示
+ 1353 - 0
app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt


+ 46 - 0
app/src/main/java/org/yameida/worktool/utils/CacheUtil.kt

@@ -0,0 +1,46 @@
+package org.yameida.worktool.utils
+
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.Utils
+import java.io.File
+import kotlin.concurrent.thread
+
+object CacheUtil {
+
+    /**
+     * 异步执行
+     * 删除7天前的下载文件
+     */
+    fun autoDelete() {
+        thread {
+            LogUtils.d("执行自动清除缓存任务")
+            val externalFilesDir = Utils.getApp().getExternalFilesDir("share")
+            if (externalFilesDir != null && externalFilesDir.isDirectory) {
+                val count = deleteOldFiles(System.currentTimeMillis() - 7 * 86400 * 1000, externalFilesDir)
+                LogUtils.d("已清除文件数: $count")
+            } else {
+                LogUtils.d("未发现缓存文件夹")
+            }
+        }
+    }
+
+    private fun deleteOldFiles(deleteTime: Long, directory: File?): Int {
+        var count = 0
+        if (directory != null && directory.isDirectory) {
+            val files = directory.listFiles()
+            if (files != null) {
+                for (file in files) {
+                    if (file.isDirectory) {
+                        count += deleteOldFiles(deleteTime, file)
+                    } else {
+                        if (file.lastModified() < deleteTime) {
+                            file.delete()
+                            count += 1
+                        }
+                    }
+                }
+            }
+        }
+        return count
+    }
+}

+ 33 - 0
app/src/main/java/org/yameida/worktool/utils/DonateUtil.kt

@@ -0,0 +1,33 @@
+package org.yameida.worktool.utils
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.blankj.utilcode.util.ToastUtils
+import com.blankj.utilcode.util.Utils
+import com.qmuiteam.qmui.widget.dialog.QMUIDialog
+import org.yameida.worktool.R
+
+object DonateUtil {
+
+    fun zfbDonate(context: Context) {
+        try {
+            QMUIDialog.MessageDialogBuilder(context)
+                .setTitle(context.getString(R.string.host_list))
+                .setTitle("捐赠")
+                .setMessage("如果你觉得${context.getString(R.string.app_name)}很棒,可否愿意花一点点钱请作者喝杯咖啡")
+                .addAction("支付宝") {
+                    dialog, index -> dialog.dismiss()
+                    ToastUtils.showLong(Utils.getApp().getString(R.string.app_name) + " 因为有你的支持而能够不断更新、完善,非常感谢支持!")
+                    val intent = Intent(Intent.ACTION_VIEW)
+                    intent.data = Uri.parse("alipays://platformapi/startapp?saId=10000007&clientVersion=3.7.0.0718&qrcode=https%3A%2F%2Fqr.alipay.com%2Ffkx15436xnv3mzpuufhvn52%3F_s%3Dweb-other")
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    context.startActivity(intent)
+                }
+                .create(R.style.QMUI_Dialog)
+                .show()
+        } catch (e: Throwable) {
+            ToastUtils.showShort("打开支付宝失败,你可能还没有安装支付宝客户端")
+        }
+    }
+}

+ 53 - 0
app/src/main/java/org/yameida/worktool/utils/ExtensionMethod.kt

@@ -0,0 +1,53 @@
+package org.yameida.worktool.utils
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.view.View
+import com.blankj.utilcode.util.EncryptUtils
+
+
+
+fun getFileMD5(file: java.io.File): String = file.getMD5()
+
+fun java.io.File.getMD5(): String {
+  val digest = EncryptUtils.encryptMD5File(this)
+  val strHex = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f")
+  val sb = StringBuilder()
+  for (byte in digest) {
+    var d = byte.toInt()
+    if (d < 0) d += 256
+    val d1 = d / 16
+    val d2 = d % 16
+    sb.append(strHex[d1] + strHex[d2])
+  }
+  return sb.toString()
+}
+
+fun Long.toTimeString(): String {
+  val i = this.toInt() / 1000
+  return String.format("%02d:%02d", Integer.valueOf(i / 60), Integer.valueOf(i % 60))
+}
+
+fun Context.startServiceSafe(intent: Intent) {
+  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+    startForegroundService(intent)
+  } else {
+    startService(intent)
+  }
+}
+
+fun Activity.hideBottomNav() {
+  val decorView = this.window.decorView
+  decorView.systemUiVisibility = 0
+  val uiOptions = (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+          or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN or
+          View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
+  decorView.setSystemUiVisibility(uiOptions)
+}
+
+fun Activity.showBottomNav() {
+  val decorView = this.window.decorView
+  decorView.systemUiVisibility = 0
+}

+ 150 - 0
app/src/main/java/org/yameida/worktool/utils/FloatWindowHelper.kt

@@ -0,0 +1,150 @@
+package org.yameida.worktool.utils
+
+import android.accessibilityservice.AccessibilityService
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import android.os.Message
+import android.view.View
+import android.widget.ImageView
+import com.blankj.utilcode.util.FileUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.ToastUtils
+import com.blankj.utilcode.util.Utils
+import com.bumptech.glide.Glide
+import org.yameida.floatwindow.DefaultFloatService
+import org.yameida.floatwindow.FloatWindowManager
+import org.yameida.floatwindow.listener.OnClickListener
+import org.yameida.worktool.Constant
+import org.yameida.worktool.R
+import org.yameida.worktool.activity.ListenActivity
+import org.yameida.worktool.model.WeworkMessageBean
+import org.yameida.worktool.service.MyLooper
+import org.yameida.worktool.service.WeworkController
+import org.yameida.worktool.service.getRoot
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.concurrent.thread
+
+object FloatWindowHelper {
+
+    var isPause = false
+
+    fun showWindow() {
+        LogUtils.d("FloatWindowHelper.showWindow()")
+
+        FloatWindowManager.show(DefaultFloatService::class.java)
+
+        val app = Utils.getApp()
+        val intent = Intent(app, DefaultFloatService::class.java)
+        app.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
+    }
+
+    /**
+     * 主功能继续
+     */
+    private fun accessibilityServiceResume() {
+        if (PermissionHelper.isAccessibilitySettingOn()) {
+            LogUtils.i("主功能继续")
+            ToastUtils.showShort("主功能继续~")
+            //隐藏软键盘模式
+            WeworkController.weworkService.softKeyboardController.showMode = AccessibilityService.SHOW_MODE_HIDDEN
+            isPause = false
+            MyLooper.getInstance().removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE)
+            MyLooper.getInstance().sendMessage(Message.obtain().apply {
+                what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
+                obj = WeworkMessageBean().apply { type = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE }
+            })
+        } else {
+            LogUtils.e("请先打开WorkTool主功能~")
+        }
+    }
+
+    /**
+     * 主功能暂停
+     */
+    private fun accessibilityServicePause() {
+        if (PermissionHelper.isAccessibilitySettingOn()) {
+            LogUtils.i("主功能暂停")
+            ToastUtils.showShort("主功能暂停~")
+            //显示软键盘模式
+            WeworkController.weworkService.softKeyboardController.showMode = AccessibilityService.SHOW_MODE_AUTO
+            isPause = true
+            WeworkController.mainLoopRunning = false
+        } else {
+            LogUtils.e("请先打开WorkTool主功能~")
+        }
+    }
+
+    private val serviceConnection = object : ServiceConnection {
+        override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) {
+            LogUtils.i("DefaultFloatService 服务连接")
+            val service = (iBinder as DefaultFloatService.DefaultFloatServiceBinder).getService()
+            service.onClickListener = object : OnClickListener {
+                override fun onClick(v: View, event: Int) {
+                    when (event) {
+                        1 -> {
+                            if (PermissionHelper.isAccessibilitySettingOn()) {
+                                if (!isPause) {
+                                    ToastUtils.showShort("请先暂停WorkTool主功能~")
+                                    return
+                                }
+                                thread {
+                                    val printNodeClazzTree =
+                                        AccessibilityUtil.printNodeClazzTree(getRoot(true))
+                                    val df = SimpleDateFormat("MMdd_HHmmss")
+                                    val filePath = "${
+                                        Utils.getApp().getExternalFilesDir("share")
+                                    }/${df.format(Date())}/${df.format(Date())}_printNode.txt"
+                                    val newFile = File(filePath)
+                                    val create = FileUtils.createFileByDeleteOldFile(newFile)
+                                    if (create && newFile.canWrite()) {
+                                        printNodeClazzTree.append("\n")
+                                            .append(WeworkController.weworkService.currentPackage)
+                                            .append("\n")
+                                            .append(WeworkController.weworkService.currentClass)
+                                        newFile.writeBytes(printNodeClazzTree.toString().toByteArray())
+                                        LogUtils.i("打印节点文件存储本地成功 $filePath", "当前页面: ${WeworkController.weworkService.currentClass}")
+                                    }
+                                    ShareUtil.share("*/*", newFile)
+                                }
+                            } else {
+                                ToastUtils.showShort("请先打开WorkTool主功能~")
+                            }
+                        }
+                        2 -> {
+                            if (PermissionHelper.isAccessibilitySettingOn()) {
+                                if (isPause) {
+                                    Glide.with(Utils.getApp()).load(R.drawable.float_icon_pause).into(v as ImageView)
+                                    accessibilityServiceResume()
+                                } else {
+                                    Glide.with(Utils.getApp()).load(R.drawable.float_icon_play).into(v as ImageView)
+                                    accessibilityServicePause()
+                                }
+                            } else {
+                                ToastUtils.showShort("请先打开WorkTool主功能~")
+                            }
+                        }
+                        3 -> {
+                            Utils.getApp().packageManager.getLaunchIntentForPackage(Constant.PACKAGE_NAMES)?.apply {
+                                this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                                Utils.getApp().startActivity(this)
+                            }
+                        }
+                        4 -> {
+                            ListenActivity.enterActivity(Utils.getApp(), 0)
+                        }
+                    }
+                }
+            }
+        }
+
+        override fun onServiceDisconnected(name: ComponentName?) {
+            LogUtils.i("DefaultFloatService 服务断开")
+        }
+    }
+
+}

+ 96 - 0
app/src/main/java/org/yameida/worktool/utils/FlowPermissionHelper.kt

@@ -0,0 +1,96 @@
+package org.yameida.worktool.utils
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import com.blankj.utilcode.util.AppUtils
+import org.yameida.worktool.utils.envcheck.CheckRoot
+import java.lang.reflect.Method
+
+object FlowPermissionHelper {
+
+    fun isBlueCloud(): Boolean {
+        val blueVersionName = AppUtils.getAppInfo("com.blue.backup")?.versionName
+        return blueVersionName != null
+    }
+
+    fun isXiaoMi(): Boolean {
+        return checkManufacturer("xiaomi")
+    }
+
+    fun isOppo(): Boolean {
+        return checkManufacturer("oppo")
+    }
+
+    fun isVivo(): Boolean {
+        return checkManufacturer("vivo")
+    }
+
+    private fun checkManufacturer(manufacturer: String): Boolean {
+        return manufacturer.equals(Build.MANUFACTURER, true)
+    }
+
+    fun canBackgroundStart(context: Context): Boolean {
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 || CheckRoot.isDeviceRooted()) {
+            return true
+        }
+
+        if (isBlueCloud()) {
+            return true
+        }
+
+        if (isXiaoMi()) {
+            return isXiaomiBgStartPermissionAllowed(context)
+        }
+
+        if (isVivo()) {
+            return isVivoBgStartPermissionAllowed(context)
+        }
+
+        if (isOppo() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return Settings.canDrawOverlays(context)
+        }
+        return true
+    }
+
+
+    private fun isXiaomiBgStartPermissionAllowed(context: Context): Boolean {
+        val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+        try {
+            val op = 10021
+            val method: Method = ops.javaClass.getMethod("checkOpNoThrow", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java)
+            val result = method.invoke(ops, op, android.os.Process.myUid(), context.packageName) as Int
+            return result == AppOpsManager.MODE_ALLOWED
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        return false
+    }
+
+    private fun isVivoBgStartPermissionAllowed(context: Context): Boolean {
+        return getVivoBgStartPermissionStatus(context) == 0
+    }
+
+    /**
+     * 判断Vivo后台弹出界面状态, 1无权限,0有权限
+     * @param context context
+     */
+    private fun getVivoBgStartPermissionStatus(context: Context): Int {
+        val uri: Uri = Uri.parse("content://com.vivo.permissionmanager.provider.permission/start_bg_activity")
+        val selection = "pkgname = ?"
+        val selectionArgs = arrayOf(context.packageName)
+        var state = 1
+        try {
+            context.contentResolver.query(uri, null, selection, selectionArgs, null)?.use {
+                if (it.moveToFirst()) {
+                    state = it.getInt(it.getColumnIndex("currentstate"))
+                }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        return state
+    }
+}

+ 8 - 0
app/src/main/java/org/yameida/worktool/utils/GenericFileProvider.kt

@@ -0,0 +1,8 @@
+package org.yameida.worktool.utils
+
+import androidx.core.content.FileProvider
+
+/**
+ * Created by Gallon on 2019/7/8.
+ */
+class GenericFileProvider: FileProvider()

+ 64 - 0
app/src/main/java/org/yameida/worktool/utils/HostTestHelper.kt

@@ -0,0 +1,64 @@
+package org.yameida.worktool.utils
+
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.ToastUtils
+import com.lzy.okgo.OkGo
+import com.lzy.okgo.callback.StringCallback
+import com.lzy.okgo.model.Response
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.WebSocket
+import okhttp3.WebSocketListener
+import okio.ByteString
+import org.yameida.worktool.Constant
+
+object HostTestHelper {
+
+    fun test() {
+        OkGo.get<String>(Constant.getTestUrl())
+            .execute(object : StringCallback() {
+                override fun onSuccess(response: Response<String>) {
+                    LogUtils.i("测试接口: " + response.body())
+                    ToastUtils.showLong("服务器连接测试成功!")
+                    return
+                }
+
+                override fun onError(response: Response<String>) {
+                    LogUtils.e("服务器连接测试失败")
+                    ToastUtils.showLong("服务器连接测试失败!" + response.exception)
+                }
+            })
+    }
+
+    fun testWs() {
+        val s = OkHttpClient().newWebSocket(Request.Builder().url(Constant.getWsUrl()).build(),
+            object : WebSocketListener() {
+                override fun onOpen(webSocket: WebSocket, response: okhttp3.Response) {
+                    ToastUtils.showLong("链接: ${Constant.getWsUrl()}\nonOpen\n" + response.body())
+                }
+
+                override fun onMessage(webSocket: WebSocket, text: String) {
+                    ToastUtils.showLong("链接: ${Constant.getWsUrl()}\nonMessage\ntext:$text")
+                    webSocket.close(1000, "接口测试成功")
+                }
+
+                override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
+                    ToastUtils.showLong("链接: ${Constant.getWsUrl()}\nonMessage\nbytes:$bytes")
+                    webSocket.close(1000, "接口测试成功")
+                }
+
+                override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
+                    ToastUtils.showLong("链接: ${Constant.getWsUrl()}\nonClosing\ncode:$code reason:$reason")
+                }
+
+                override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
+                    ToastUtils.showLong("链接: ${Constant.getWsUrl()}\nonClosed\ncode:$code reason:$reason")
+                }
+
+                override fun onFailure(webSocket: WebSocket, t: Throwable, response: okhttp3.Response?) {
+                    ToastUtils.showLong("链接: ${Constant.getWsUrl()}\nonClosed\nresponse:${response?.body()} t:${t.message}")
+                }
+            })
+    }
+
+}

+ 179 - 0
app/src/main/java/org/yameida/worktool/utils/HttpUtil.kt

@@ -0,0 +1,179 @@
+package org.yameida.worktool.utils
+
+import com.blankj.utilcode.util.*
+import com.lzy.okgo.OkGo
+import com.lzy.okgo.callback.StringCallback
+import com.lzy.okgo.model.Response
+import model.UpdateConfig
+import org.json.JSONObject
+import org.yameida.worktool.Constant
+import org.yameida.worktool.R
+import org.yameida.worktool.model.network.CheckUpdateResult
+import org.yameida.worktool.model.network.GetMyConfigResult
+import org.yameida.worktool.service.log
+import org.yameida.worktool.utils.envcheck.CheckRoot
+import update.UpdateAppUtils
+import java.io.File
+
+object HttpUtil {
+
+    /**
+     * 检查更新
+     */
+    fun checkUpdate(url: String? = null) {
+        OkGo.get<String>(url ?: Constant.getCheckUpdateUrl())
+            .execute(object : StringCallback() {
+                override fun onSuccess(response: Response<String>) {
+                    try {
+                        val commonResult =
+                            GsonUtils.fromJson(
+                                response.body(),
+                                CheckUpdateResult::class.java
+                            )
+                        if (commonResult.code != 200) {
+                            return onError(response)
+                        }
+                        LogUtils.i(commonResult.data)
+                        commonResult.data?.apply {
+                            if (AppUtils.getAppVersionCode() < this.versionCode) {
+                                UpdateAppUtils
+                                    .getInstance()
+                                    .apkUrl(this.downloadUrl)
+                                    .updateTitle(this.title)
+                                    .updateContent(this.updateLog.replace("\\n", "\n"))
+                                    .updateConfig(
+                                        UpdateConfig(
+                                            force = AppUtils.getAppVersionCode() < this.minVersionCode,
+                                            serverVersionName = this.versionName,
+                                            serverVersionCode = this.versionCode,
+                                            isShowNotification = false
+                                        )
+                                    )
+                                    .update()
+                            } else {
+                                ToastUtils.showShort(R.string.update_no_update)
+                            }
+                            return
+                        }
+                    } catch (e: Exception) {
+                        LogUtils.e(e)
+                        onError(response)
+                    }
+                }
+
+                override fun onError(response: Response<String>) {
+                    ToastUtils.showLong(R.string.update_failed)
+                    LogUtils.e("检查更新失败")
+                }
+            })
+    }
+
+    /**
+     * 获取机器人配置
+     */
+    fun getMyConfig(toast: Boolean = true) {
+        if (Constant.robotId.isBlank()) {
+            if (toast) {
+                ToastUtils.showLong("请先填写机器人ID")
+            }
+            return
+        }
+        OkGo.get<String>(Constant.getMyConfig())
+            .execute(object : StringCallback() {
+                override fun onSuccess(response: Response<String>) {
+                    try {
+                        val commonResult =
+                            GsonUtils.fromJson(
+                                response.body(),
+                                GetMyConfigResult::class.java
+                            )
+                        if (commonResult.code != 200) {
+                            return onError(response)
+                        }
+                        LogUtils.i("获取配置", commonResult.data)
+                        SPUtils.getInstance().put("risk", false)
+                        if (CheckRoot.isDeviceRooted()) {
+                            val date = TimeUtils.string2Date(commonResult.data.createTime, "yyyy-MM-dd'T'HH:mm:ss")
+                            if (System.currentTimeMillis() - date.time < 15 * 68400 * 1000) {
+                                LogUtils.e("环境监测异常,请勿使用本应用!")
+                                ToastUtils.showLong("环境监测异常,请勿使用本应用!")
+                                SPUtils.getInstance().put("risk", true)
+                            }
+                        }
+                        commonResult.data?.apply {
+                            Constant.qaUrl = this.callbackUrl ?: ""
+                            Constant.openCallback = this.openCallback ?: 0
+                            Constant.replyStrategy = (this.replyAll ?: 0) + 1
+                        }
+                    } catch (e: Exception) {
+                        LogUtils.e(e)
+                        onError(response)
+                    }
+                }
+
+                override fun onError(response: Response<String>) {
+                    ToastUtils.showLong("获取配置失败 请检查机器人ID")
+                    LogUtils.e("获取配置失败 请检查机器人ID")
+                }
+            })
+    }
+
+    /**
+     * 推送图片
+     */
+    fun pushImage(url: String, titleList: List<String>, receivedName: String?, imagePath: String, roomType: Int) {
+        return pushImage(url, titleList, receivedName, File(imagePath).readBytes(), roomType)
+    }
+
+    /**
+     * 推送图片
+     */
+    fun pushImage(url: String, titleList: List<String>, receivedName: String?, byteArray: ByteArray, roomType: Int) {
+        val json = JSONObject()
+        if (receivedName != null) {
+            json.put("receivedName", receivedName)
+            json.put("groupName", titleList.lastOrNull())
+            if (titleList.size > 1) {
+                json.put("groupRemark", titleList.first())
+            } else {
+                json.put("groupRemark", null)
+            }
+        } else {
+            json.put("receivedName", titleList.lastOrNull { !it.contains("@") } ?: "")
+            json.put("groupName", null)
+            json.put("groupRemark", null)
+        }
+        json.put("image", EncodeUtils.base64Encode2String(byteArray))
+        json.put("robotId", Constant.robotId)
+        json.put("roomType", roomType)
+        json.put("atMe", false)
+        json.put("textType", 2)
+        OkGo.post<String>(url)
+            .upJson(json)
+            .execute(object : StringCallback() {
+                override fun onSuccess(response: Response<String>) {
+                    LogUtils.d("推送图片成功: ${titleList.joinToString()} $receivedName")
+                    log("推送图片成功: ${titleList.joinToString()} $receivedName")
+                }
+
+                override fun onError(response: Response<String>) {
+                    ToastUtils.showLong("推送图片失败")
+                    LogUtils.e("推送图片失败")
+                    error("推送图片失败: ${titleList.joinToString()} $receivedName")
+                }
+            })
+    }
+
+    /**
+     * 推送本地文件
+     */
+    fun pushLocalFile(file: File) {
+        OkGo.post<String>(Constant.getPushLocalFileUrl())
+            .addFileParams("file", listOf(file))
+            .execute(object : StringCallback() {
+                override fun onSuccess(response: Response<String>?) {
+                    LogUtils.d("推送本地文件成功: ${file.absolutePath}")
+                }
+            })
+    }
+}

+ 140 - 0
app/src/main/java/org/yameida/worktool/utils/IWWAPIUtil.kt

@@ -0,0 +1,140 @@
+package org.yameida.worktool.utils
+
+import android.content.Context
+import android.graphics.Bitmap
+import com.blankj.utilcode.util.*
+import com.lzy.okgo.OkGo
+import com.tencent.wework.api.IWWAPI
+import com.tencent.wework.api.WWAPIFactory
+import com.tencent.wework.api.model.WWMediaImage
+import com.tencent.wework.api.model.WWMediaLink
+import com.tencent.wework.api.model.WWMediaMiniProgram
+import com.tencent.wework.api.model.WWSimpleRespMessage
+import org.yameida.worktool.service.log
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
+
+
+object IWWAPIUtil {
+
+    private var iwwapi: IWWAPI? = null
+
+    fun init(context: Context) {
+        iwwapi = WWAPIFactory.createWWAPI(context)
+        iwwapi?.unregisterApp()
+        val result = iwwapi?.registerApp(SPUtils.getInstance().getString("weworkSchema"))
+    }
+
+    fun init(){
+        iwwapi?.unregisterApp()
+        val result = iwwapi?.registerApp(SPUtils.getInstance().getString("weworkSchema"))
+    }
+
+    fun sendLink(
+        appId: String?,
+        agentId: String?,
+        thumbUrl: String?,
+        webpageUrl: String?,
+        title: String?,
+        description: String?
+    ): Boolean {
+        val link = WWMediaLink()
+        link.thumbUrl = thumbUrl
+        link.webpageUrl = webpageUrl
+        link.title = title
+        link.description = description
+        link.appPkg = AppUtils.getAppPackageName()
+        link.appName = AppUtils.getAppName()
+        link.appId = appId
+        link.agentId = agentId
+        return iwwapi?.sendMessage(link) ?: false
+    }
+
+    fun sendMicroProgram(
+        appId: String?,
+        agentId: String?,
+        title: String?,
+        imageUrl: String?,
+        username: String?,
+        path: String?,
+        schema: String?,
+        description: String?
+    ): Boolean {
+        val miniProgram = WWMediaMiniProgram()
+        miniProgram.appPkg = "com.rk.worktool"
+        miniProgram.appName = "worktool"
+        miniProgram.appId = appId
+        miniProgram.agentId = agentId
+        miniProgram.schema = schema
+        miniProgram.username = username //必须是应用关联的小程序,注意要有@app后缀
+        miniProgram.description = description
+        miniProgram.path = path
+        miniProgram.title = title
+        LogUtils.d("小程序: ${miniProgram.appId} ${miniProgram.agentId} ${miniProgram.username} ${miniProgram.title}")
+        log("小程序: ${miniProgram.appId} ${miniProgram.agentId} ${miniProgram.username} ${miniProgram.title}")
+//        val bitmap = (Utils.getApp().getDrawable(R.mipmap.ic_launcher) as BitmapDrawable).bitmap
+//        val stream = ByteArrayOutputStream()
+//        bitmap.compress(Bitmap.CompressFormat.JPEG, 75, stream)
+
+//        if (imageUrl != null) {
+//            miniProgram.hdImageData = imageUrl.toByteArray()
+//        }
+
+//        val miniProgram = WWMediaImage()
+//        miniProgram.fileName = "006YOSmagy1hw227uisk4j30sg0sgq6t"
+//        miniProgram.filePath = "https://ww3.sinaimg.cn/mw690/006YOSmagy1hw227uisk4j30sg0sgq6t.jpg"
+//        miniProgram.appPkg = "com.rk.worktool"
+//        miniProgram.appName = "worktool"
+//        miniProgram.appId = "ww5cbd509b435e591b" //企业唯一标识。创建企业后显示在,我的企业 CorpID字段
+//        miniProgram.agentId = "1000015"
+        if (imageUrl != null) {
+            LogUtils.i("下载开始 $imageUrl")
+            val execute = OkGo.get<File>(imageUrl).execute()
+            LogUtils.i("下载完成 $imageUrl")
+            val body = execute.body()
+            if (body != null) {
+                val df = SimpleDateFormat("yyyy-MM-dd")
+                val filePath = "${
+                    Utils.getApp().getExternalFilesDir("mp_image_cache")
+                }/${df.format(Date())}/share.png"
+                val newFile = File(filePath)
+                val create = FileUtils.createFileByDeleteOldFile(newFile)
+                if (create && newFile.canWrite()) {
+                    newFile.writeBytes(body.bytes())
+                    LogUtils.i("文件存储本地成功 $filePath")
+                    val bitmap = ImageUtils.bytes2Bitmap(File(filePath).readBytes())
+                    val stream = ByteArrayOutputStream()
+                    bitmap.compress(Bitmap.CompressFormat.JPEG, 75, stream)
+                    miniProgram.hdImageData = stream.toByteArray()
+                } else {
+                    LogUtils.e("文件存储本地失败 $filePath")
+                }
+            } else {
+                LogUtils.e("文件下载失败")
+            }
+        }
+        return iwwapi?.sendMessage(miniProgram) { resp ->
+            if (resp is WWSimpleRespMessage) {
+
+            }
+        } ?: false
+    }
+
+    fun sendPicture(
+        path: String,
+        appId: String,
+        agentId: String,
+        fileName: String
+    ): Boolean {
+        val img = WWMediaImage()
+        img.fileName = fileName
+        img.filePath = path
+        img.appPkg = "com.rk.worktool"
+        img.appName = "worktool"
+        img.appId = appId //企业唯一标识。创建企业后显示在,我的企业 CorpID字段
+        img.agentId = agentId
+        return iwwapi?.sendMessage(img) ?: false
+    }
+}

+ 26 - 0
app/src/main/java/org/yameida/worktool/utils/ImageDepthSizeUtil.java

@@ -0,0 +1,26 @@
+package org.yameida.worktool.utils;
+
+import com.blankj.utilcode.util.LogUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+public class ImageDepthSizeUtil {
+
+    public static boolean checkRawImage(String path) {
+        try {
+            File file = new File(path);
+            FileInputStream fileInputStream = new FileInputStream(file);
+            byte[] b = new byte[1000000];
+            fileInputStream.read(b);
+            int bitsPerPixel = (b[25] & 0xff) == 2 || (b[25] & 0xff) == 6 ? (b[24] & 0xff) * 3 : b[24] & 0xff;
+            LogUtils.v("path: " + path, "bitsPerPixel: " + bitsPerPixel);
+            return bitsPerPixel == 24;
+        } catch (IOException e) {
+            LogUtils.e("ImageDepthSize Check Error", e);
+        }
+        return false;
+    }
+
+}

文件差異過大導致無法顯示
+ 1230 - 0
app/src/main/java/org/yameida/worktool/utils/ImageInfo.java


+ 52 - 0
app/src/main/java/org/yameida/worktool/utils/LogUtilsInit.kt

@@ -0,0 +1,52 @@
+package org.yameida.worktool.utils
+
+import com.blankj.utilcode.util.AppUtils
+import com.blankj.utilcode.util.LogUtils
+import org.yameida.worktool.Constant
+import java.util.*
+
+object LogUtilsInit {
+
+    /**
+     * 参考
+     *
+    LogUtils.Config config =
+    LogUtils.getConfig()
+    .setLogSwitch(true) // 设置 log 总开关,包括输出到控制台和文件,默认开
+    .setConsoleSwitch(AppUtils.isAppDebug()) // 设置是否输出到控制台开关,默认开
+    .setGlobalTag(null) // 设置 log 全局标签,默认为空
+    // 当全局标签不为空时,我们输出的 log 全部为该 tag,
+    // 为空时,如果传入的 tag 为空那就显示类名,否则显示 tag
+    .setLogHeadSwitch(true) // 设置 log 头信息开关,默认为开
+    .setLog2FileSwitch(true) // 打印 log 时是否存到文件的开关,默认关
+    .setDir("") // 当自定义路径为空时,写入应用的/cache/log/目录中
+    .setFilePrefix("LYan") // 当文件前缀为空时,默认为"util",即写入文件为"util-yyyy-MM-dd$fileExtension"
+    .setFileExtension(".log") // 设置日志文件后缀
+    .setBorderSwitch(AppUtils.isAppDebug()) // 输出日志是否带边框开关,默认开
+    .setSingleTagSwitch(true) // 一条日志仅输出一条,默认开,为美化 AS 3.1 的 Logcat
+    .setConsoleFilter(LogUtils.V) // log 的控制台过滤器,和 logcat 过滤器同理,默认 Verbose
+    .setFileFilter(LogUtils.I) // log 文件过滤器,和 logcat 过滤器同理,默认 Verbose
+    .setStackDeep(1) // log 栈深度,默认为 1
+    .setStackOffset(0) // 设置栈偏移,比如二次封装的话就需要设置,默认为 0
+    .setSaveDays(7) // 设置日志可保留天数,默认为 -1 表示无限时长
+     *
+     */
+    fun init() {
+        val prefix = try {
+            AppUtils.getAppSignaturesMD5().firstOrNull()?.replace(":", "")
+                ?.substring(0, 2)?.toLowerCase(Locale.ROOT)
+        } catch (e: Exception) {
+            null
+        }
+        LogUtils.getConfig().apply {
+            isLog2FileSwitch = true
+            saveDays = 7
+            filePrefix = prefix
+        }
+        if (FlowPermissionHelper.isBlueCloud()) {
+            Constant.customLink = true
+            Constant.customMP = true
+        }
+    }
+
+}

+ 51 - 0
app/src/main/java/org/yameida/worktool/utils/OkHttpUtil.kt

@@ -0,0 +1,51 @@
+package org.yameida.worktool.utils
+
+import okhttp3.OkHttpClient
+import java.security.cert.CertificateException
+import java.security.cert.X509Certificate
+import java.util.concurrent.TimeUnit
+import javax.net.ssl.*
+
+object OkHttpUtil {
+
+    val okHttpClient = getUnsafeOkHttpClient()
+
+    private fun getUnsafeOkHttpClient(): OkHttpClient {
+        try {
+            // Create a trust manager that does not validate certificate chains
+            val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
+                override fun getAcceptedIssuers(): Array<X509Certificate> {
+                    return arrayOf()
+                }
+
+                @Throws(CertificateException::class)
+                override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
+                }
+
+                @Throws(CertificateException::class)
+                override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
+                }
+            })
+
+            // Install the all-trusting trust manager
+            val sslContext = SSLContext.getInstance("SSL")
+            sslContext.init(null, trustAllCerts, java.security.SecureRandom())
+            // Create an ssl socket factory with our all-trusting manager
+            val sslSocketFactory = sslContext.socketFactory
+
+            val builder = OkHttpClient.Builder()
+            builder.sslSocketFactory(sslSocketFactory)
+            builder.hostnameVerifier(object : HostnameVerifier {
+                override fun verify(hostname: String, session: SSLSession): Boolean {
+                    return true
+                }
+            })
+            builder.readTimeout(20, TimeUnit.SECONDS)
+            builder.writeTimeout(20, TimeUnit.SECONDS)
+            return builder.build()
+        } catch (e: Exception) {
+            throw RuntimeException(e)
+        }
+    }
+
+}

+ 51 - 0
app/src/main/java/org/yameida/worktool/utils/PermissionHelper.kt

@@ -0,0 +1,51 @@
+package org.yameida.worktool.utils
+
+import android.provider.Settings
+import android.text.TextUtils
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.Utils
+import org.yameida.worktool.service.WeworkService
+
+/**
+ * 无障碍服务开启辅助类
+ */
+object PermissionHelper {
+
+    fun isAccessibilitySettingOn(): Boolean {
+        val context = Utils.getApp()
+        var enable = 0
+        val canonicalName = WeworkService::class.java.canonicalName ?: ""
+        val serviceName = context.packageName + "/" + canonicalName
+        val serviceShortName = context.packageName + "/" + canonicalName.replace(context.packageName, "")
+        try {
+            enable = Settings.Secure.getInt(
+                context.contentResolver,
+                Settings.Secure.ACCESSIBILITY_ENABLED,
+                0
+            )
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        var flag = false
+        if (enable == 1) {
+            val stringSplitter = TextUtils.SimpleStringSplitter(':')
+            val settingVal = Settings.Secure.getString(
+                context.contentResolver,
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
+            )
+            if (settingVal != null) {
+                stringSplitter.setString(settingVal)
+                while (stringSplitter.hasNext()) {
+                    val accessibilityService = stringSplitter.next()
+                    if (accessibilityService == serviceName || accessibilityService == serviceShortName) {
+                        flag = true
+                        break
+                    }
+                }
+            }
+        }
+        LogUtils.v("isAccessibilitySettingOn: $serviceName $serviceShortName $flag")
+        return flag
+    }
+
+}

+ 225 - 0
app/src/main/java/org/yameida/worktool/utils/PermissionPageManagement.java

@@ -0,0 +1,225 @@
+package org.yameida.worktool.utils;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * 跳转到APP的权限配置页
+ */
+public class PermissionPageManagement {
+
+    private static final String TAG = "JumpPermissionManagement";
+
+    /**
+     * Build.MANUFACTURER
+     */
+    private static final String MANUFACTURER_HUAWEI = "HUAWEI";//华为
+    private static final String MANUFACTURER_MEIZU = "Meizu";//魅族
+    private static final String MANUFACTURER_XIAOMI = "Xiaomi";//小米
+    private static final String MANUFACTURER_SONY = "Sony";//索尼
+    private static final String MANUFACTURER_OPPO = "OPPO";//oppo
+    private static final String MANUFACTURER_LG = "LG";
+    private static final String MANUFACTURER_VIVO = "vivo";//vivo
+    private static final String MANUFACTURER_SAMSUNG = "samsung";//三星
+    private static final String MANUFACTURER_ZTE = "ZTE";//中兴
+    private static final String MANUFACTURER_YULONG = "YuLong";//酷派
+    private static final String MANUFACTURER_LENOVO = "LENOVO";//联想
+
+    /**
+     * 此函数可以自己定义
+     * @param activity
+     */
+    public static void goToSetting(Activity activity){
+        try {
+            switch (Build.MANUFACTURER) {
+                case MANUFACTURER_HUAWEI:
+                    Huawei(activity);
+                    break;
+                case MANUFACTURER_MEIZU:
+                    Meizu(activity);
+                    break;
+                case MANUFACTURER_XIAOMI:
+                    Xiaomi(activity);
+                    break;
+                case MANUFACTURER_SONY:
+                    Sony(activity);
+                    break;
+                case MANUFACTURER_OPPO:
+                    OPPO(activity);
+                    break;
+                case MANUFACTURER_VIVO:
+                    VIVO(activity);
+                    break;
+                case MANUFACTURER_LG:
+                    LG(activity);
+                    break;
+                default:
+                    ApplicationInfo(activity);
+                    Log.e("goToSetting", "目前暂不支持此系统");
+                    break;
+            }
+        } catch (Exception e) {
+            Log.e("goToSetting", "Error 目前暂不支持此系统");
+            ApplicationInfo(activity);
+        }
+    }
+
+    public static void Huawei(Activity activity) {
+        try {
+            Intent intent = new Intent();
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.putExtra("packageName", activity.getApplicationInfo().packageName);
+            ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");
+            intent.setComponent(comp);
+            activity.startActivity(intent);
+        } catch (Exception e) {
+            e.printStackTrace();
+            goIntentSetting(activity);
+        }
+    }
+
+    public static void Meizu(Activity activity) {
+        try {
+            Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
+            intent.addCategory(Intent.CATEGORY_DEFAULT);
+            intent.putExtra("packageName", activity.getPackageName());
+            activity.startActivity(intent);
+        } catch (Exception e) {
+            e.printStackTrace();
+            goIntentSetting(activity);
+        }
+    }
+
+    public static void Xiaomi(Activity activity) {
+        try {
+            Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
+            intent.putExtra("extra_pkgname", activity.getPackageName());
+            ComponentName componentName = new ComponentName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
+            intent.setComponent(componentName);
+            activity.startActivity(intent);
+        } catch (Exception e) {
+            e.printStackTrace();
+            goIntentSetting(activity);
+        }
+    }
+
+    public static void Sony(Activity activity) {
+        try {
+            Intent intent = new Intent();
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.putExtra("packageName", activity.getPackageName());
+            ComponentName comp = new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity");
+            intent.setComponent(comp);
+            activity.startActivity(intent);
+        } catch (Exception e) {
+            e.printStackTrace();
+            goIntentSetting(activity);
+        }
+    }
+
+    public static void OPPO(Activity activity) {
+        try {
+            Intent intent = new Intent();
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.putExtra("packageName", activity.getPackageName());
+            //        ComponentName comp = new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity");
+            ComponentName comp = new ComponentName("com.coloros.securitypermission", "com.coloros.securitypermission.permission.PermissionAppAllPermissionActivity");//R11t 7.1.1 os-v3.2
+            intent.setComponent(comp);
+            activity.startActivity(intent);
+        } catch (Exception e) {
+            e.printStackTrace();
+            goIntentSetting(activity);
+        }
+    }
+
+    public static void VIVO(Activity activity) {
+        Intent localIntent;
+        if (((Build.MODEL.contains("Y85")) && (!Build.MODEL.contains("Y85A"))) || (Build.MODEL.contains("vivo Y53L"))) {
+            localIntent = new Intent();
+            localIntent.setClassName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.PurviewTabActivity");
+            localIntent.putExtra("packagename", activity.getPackageName());
+            localIntent.putExtra("tabId", "1");
+            activity.startActivity(localIntent);
+        } else {
+            localIntent = new Intent();
+            localIntent.setClassName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.SoftPermissionDetailActivity");
+            localIntent.setAction("secure.intent.action.softPermissionDetail");
+            localIntent.putExtra("packagename", activity.getPackageName());
+            activity.startActivity(localIntent);
+        }
+    }
+
+    public static void LG(Activity activity) {
+        try {
+            Intent intent = new Intent("android.intent.action.MAIN");
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.putExtra("packageName", activity.getPackageName());
+            ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity");
+            intent.setComponent(comp);
+            activity.startActivity(intent);
+        } catch (Exception e) {
+            e.printStackTrace();
+            goIntentSetting(activity);
+        }
+    }
+
+    /**
+     * 只能打开到自带安全软件
+     * @param activity
+     */
+    public static void _360(Activity activity) {
+        Intent intent = new Intent("android.intent.action.MAIN");
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("packageName", activity.getPackageName());
+        ComponentName comp = new ComponentName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity");
+        intent.setComponent(comp);
+        activity.startActivity(intent);
+    }
+
+    /**
+     * 应用信息界面
+     * @param activity
+     */
+    public static void ApplicationInfo(Activity activity){
+        Intent localIntent = new Intent();
+        localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (Build.VERSION.SDK_INT >= 9) {
+            localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
+            localIntent.setData(Uri.fromParts("package", activity.getPackageName(), null));
+        } else if (Build.VERSION.SDK_INT <= 8) {
+            localIntent.setAction(Intent.ACTION_VIEW);
+            localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
+            localIntent.putExtra("com.android.settings.ApplicationPkgName", activity.getPackageName());
+        }
+        activity.startActivity(localIntent);
+    }
+
+    /**
+     * 系统设置界面
+     * @param activity
+     */
+    public static void SystemConfig(Activity activity) {
+        Intent intent = new Intent(Settings.ACTION_SETTINGS);
+        activity.startActivity(intent);
+    }
+
+    /**
+     * 默认打开应用详细页
+     */
+    private static void goIntentSetting(Activity pActivity) {
+        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        Uri uri = Uri.fromParts("package", pActivity.getPackageName(), null);
+        intent.setData(uri);
+        try {
+            pActivity.startActivity(intent);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+}

+ 41 - 0
app/src/main/java/org/yameida/worktool/utils/PropUtil.kt

@@ -0,0 +1,41 @@
+package org.yameida.worktool.utils
+
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.ShellUtils
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStreamReader
+
+/**
+ * 系统属性工具类
+ */
+object PropUtil {
+
+    const val propVideo = "persist.lg.sourc_video"
+
+    fun getProp(propName: String = propVideo) {
+        try {
+            val process = Runtime.getRuntime().exec("getprop $propName")
+            val ir = InputStreamReader(process.inputStream)
+            val input = BufferedReader(ir)
+            var str: String? = null
+            while (input.readLine().also { str = it } != null) {
+                LogUtils.i("$propName: $str")
+            }
+        } catch (e: IOException) {
+            e.printStackTrace()
+        }
+    }
+
+    fun setProp(propName: String = propVideo, propValue: String = "/storage/emulated/0/Download/1.mp4") {
+        try {
+            ShellUtils.execCmd("setprop $propName $propValue", true, false)
+        } catch (e: IOException) {
+            e.printStackTrace()
+        } catch (e: InterruptedException) {
+            e.printStackTrace()
+        }
+        getProp(propName)
+    }
+
+}

+ 24 - 0
app/src/main/java/org/yameida/worktool/utils/RegexHelper.kt

@@ -0,0 +1,24 @@
+package org.yameida.worktool.utils
+
+object RegexHelper {
+
+    fun reverseRegexTitle(string: String): String {
+        return string.replace("\\", "\\\\")
+            .replace("*", "\\*")
+            .replace("+", "\\+")
+            .replace(".", "\\.")
+            .replace("[", "\\[")
+            .replace("]", "\\]")
+            .replace("?", "\\?")
+            .replace("^", "\\^")
+            .replace("$", "\\$")
+            .replace("{", "\\{")
+            .replace("}", "\\}")
+            .replace("|", "\\|")
+            //企微自身存在限制
+            .replace("-", "\\-")
+            .replace("(", "\\(")
+            .replace(")", "\\)")
+    }
+
+}

+ 26 - 0
app/src/main/java/org/yameida/worktool/utils/RuntimeUtil.kt

@@ -0,0 +1,26 @@
+package org.yameida.worktool.utils
+
+import java.io.BufferedReader
+import java.io.InputStream
+import java.io.InputStreamReader
+
+object RuntimeUtil {
+
+    @Throws(Exception::class)
+    fun exec(cmd: String): String? {
+        var ret: String? = null
+        val p = Runtime.getRuntime().exec(arrayOf("sh", "-c", cmd))
+        val inputStream: InputStream = p.inputStream
+        val reader = BufferedReader(InputStreamReader(inputStream))
+        var line: String? = reader.readLine()
+        while (line != null) {
+            ret = line
+            line = reader.readLine()
+        }
+        p.waitFor()
+        inputStream.close()
+        reader.close()
+        p.destroy()
+        return ret
+    }
+}

+ 67 - 0
app/src/main/java/org/yameida/worktool/utils/ShareCommentsUtil.java

@@ -0,0 +1,67 @@
+package org.yameida.worktool.utils;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.Environment;
+
+import com.blankj.utilcode.util.ToastUtils;
+
+import org.yameida.worktool.R;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+
+public class ShareCommentsUtil {
+    private static boolean checkInstallation(Context context, String packageName) {
+        try {
+            context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    public static void shareToWeChat(Context context) {
+        try {
+            if (!checkInstallation(context, "com.tencent.mm")) {
+                ToastUtils.showShort("未发现微信");
+                return;
+            }
+            Intent intent = new Intent(); //分享精确到微信的页面,朋友圈页面,或者选择好友分享页面
+            ComponentName comp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI");
+            intent.setComponent(comp);
+            intent.setAction(Intent.ACTION_SEND_MULTIPLE);
+            intent.setType("image/*"); // intent.setType("text/plain");添加Uri图片地址
+            String msg = "我想分享";
+            intent.putExtra("Kdescription", msg);
+            ArrayList<Uri> imageUris = new ArrayList<Uri>();
+            File dir = context.getExternalFilesDir(null);
+            if (dir == null || dir.getAbsolutePath().equals("")) {
+                dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath());
+            }
+            File pic = new File(dir, "bigbang.jpg");
+            pic.deleteOnExit();
+            BitmapDrawable bitmapDrawable;
+            bitmapDrawable = (BitmapDrawable) context.getDrawable(R.mipmap.ic_launcher);
+            try {
+                bitmapDrawable.getBitmap().compress(Bitmap.CompressFormat.JPEG, 75, new FileOutputStream(pic));
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+            }
+            Uri uri = Uri.parse(android.provider.MediaStore.Images.Media.insertImage(context.getContentResolver(), pic.getAbsolutePath(), "bigbang.jpg", null));
+            imageUris.add(uri);
+            intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
+            ((Activity) context).startActivityForResult(intent, 1000);
+        } catch (Throwable e) {
+            ToastUtils.showShort("分享到微信失败");
+        }
+    }
+}

+ 78 - 0
app/src/main/java/org/yameida/worktool/utils/ShareUtil.kt

@@ -0,0 +1,78 @@
+package org.yameida.worktool.utils
+
+import android.content.Intent
+import android.provider.Settings
+import androidx.core.content.FileProvider
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.Utils
+import org.yameida.worktool.Constant
+import org.yameida.worktool.service.WeworkController
+import java.io.File
+
+/**
+ * 文件分享工具类
+ */
+object ShareUtil {
+
+    /**
+     * Share Text
+     */
+    const val TEXT = "text/plain"
+
+    /**
+     * Share Image
+     */
+    const val IMAGE = "image/*"
+
+    /**
+     * Share Audio
+     */
+    const val AUDIO = "audio/*"
+
+    /**
+     * Share Video
+     */
+    const val VIDEO = "video/*"
+
+    /**
+     * Share File
+     */
+    const val File = "*/*"
+
+    /**
+     * 文件分享 需要先授权显示悬浮窗
+     */
+    fun share(type: String, path: String): Boolean {
+        return share(type, File(path))
+    }
+
+    /**
+     * 文件分享 需要先授权显示悬浮窗
+     */
+    fun share(type: String, file: File, auto: Boolean = true): Boolean {
+        val app = Utils.getApp()
+        if (auto) {
+            val root = WeworkController.weworkService.rootInActiveWindow
+            if (root.packageName != Constant.PACKAGE_NAMES) {
+                LogUtils.e("文件分享失败 当前应用不在前台")
+                return false
+            }
+            if (!Settings.canDrawOverlays(app)) {
+                LogUtils.e("文件分享失败 没有悬浮窗权限~")
+                return false
+            }
+        }
+        val intent = Intent().apply {
+            action = Intent.ACTION_SEND
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            this.type = type
+            val fileURI = FileProvider.getUriForFile(app, app.packageName + ".fileprovider", file)
+            putExtra(Intent.EXTRA_STREAM, fileURI)
+        }
+        intent.setPackage(Constant.PACKAGE_NAMES)
+        app.startActivity(Intent.createChooser(intent, "WorkTool文件分享").apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK })
+        LogUtils.e("分享了 $type ${file.absolutePath}")
+        return true
+    }
+
+}

+ 33 - 0
app/src/main/java/org/yameida/worktool/utils/StringFeatureUtil.java

@@ -0,0 +1,33 @@
+package org.yameida.worktool.utils;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+public class StringFeatureUtil {
+
+    public static int generateFeatureValue(String input) {
+        try {
+            // 计算MD5哈希值
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] md5Hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
+
+            // 计算字符串长度并取余
+            int length = input.length() % 1024;
+
+            // 将MD5哈希值的前面位数组成int值
+            int md5Value = 0;
+            for (int i = 0; i < Math.min(4, md5Hash.length); i++) {
+                md5Value = (md5Value << 8) | (md5Hash[i] & 0xFF);
+            }
+
+            // 将字符串长度合并到特征值的最后10位
+            int featureValue = (md5Value << 10) | length;
+
+            return featureValue;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return -1; // 错误情况下返回-1
+        }
+    }
+
+}

+ 19 - 0
app/src/main/java/org/yameida/worktool/utils/Views.java

@@ -0,0 +1,19 @@
+package org.yameida.worktool.utils;
+
+public class Views {
+    public static String View = "android.view.View";
+    public static String ListView = "android.widget.ListView";
+    public static String TextView = "android.widget.TextView";
+    public static String Button = "android.widget.Button";
+    public static String EditText = "android.widget.EditText";
+    public static String ViewGroup = "android.view.ViewGroup";
+    public static String RecyclerView = "androidx.recyclerview.widget.RecyclerView";
+    public static String ImageView = "android.widget.ImageView";
+    public static String GridView = "android.widget.GridView";
+    public static String RelativeLayout = "android.widget.RelativeLayout";
+    public static String LinearLayout = "android.widget.LinearLayout";
+    public static String ProgressBar = "android.widget.ProgressBar";
+    public static String ScrollView = "android.widget.ScrollView";
+    public static String CheckBox = "android.widget.CheckBox";
+    public static String FrameLayout = "android.widget.FrameLayout";
+}

+ 182 - 0
app/src/main/java/org/yameida/worktool/utils/WebSocketManager.java

@@ -0,0 +1,182 @@
+package org.yameida.worktool.utils;
+
+import android.util.Log;
+
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.TimeUtils;
+import com.hjq.toast.ToastUtils;
+
+import org.yameida.worktool.Constant;
+import org.yameida.worktool.model.WeworkMessageBean;
+import org.yameida.worktool.model.WeworkMessageListBean;
+import org.yameida.worktool.service.WeworkController;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+
+public class WebSocketManager {
+
+    public static final String HEARTBEAT = "{\"type\":" + WeworkMessageBean.HEART_BEAT + "}";
+    private static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+    public static Map<String, WebSocketManager> webSocketManager = new ConcurrentHashMap<>();
+    private static final int reconnectInt = 5000;  //毫秒
+    private static final long heartBeatRate = 5;  //秒
+    private Map<String, Long> messageIdMap = new ConcurrentHashMap<>();
+    private ScheduledFuture task;
+    private WebSocket socket;
+    private String url;
+    private WebSocketListener listener;
+    private boolean connecting = false;
+    private long lastConnectedTime = 0L;
+
+    public WebSocketManager(String url, WebSocketListener listener) {
+        Log.e(url, "新建链接");
+        this.url = url;
+        this.listener = listener;
+        OkHttpClient client = new OkHttpClient();
+        Request request = new Request.Builder().url(url).build();
+        this.socket = client.newWebSocket(request, listener);
+        socket.send("{\"td\":" + System.currentTimeMillis() + "}");
+        webSocketManager.put(url, this);
+        task = heartCheckStart();
+    }
+
+    public void send(WeworkMessageBean msg) {
+        send(new WeworkMessageListBean(msg, WeworkMessageListBean.SOCKET_TYPE_MESSAGE_LIST, null, null));
+    }
+
+    /**
+     * 确认消息
+     * @param messageId 保存在map中30秒后清除
+     * @return true继续消费事件 false重复事件不需消息
+     */
+    public synchronized boolean confirm(WeworkMessageListBean message) {
+        message.setSocketType(WeworkMessageListBean.SOCKET_TYPE_MESSAGE_CONFIRM);
+        send(message);
+        message.setMessageId(TimeUtils.date2String(new Date()).replace(" ", "#") + "#" + UUID.randomUUID());
+        if (messageIdMap.containsKey(message.getMessageId())) return false;
+        long currentTimeMillis = System.currentTimeMillis();
+        messageIdMap.put(message.getMessageId(), currentTimeMillis + 30 * 1000);
+        for (Map.Entry<String, Long> entry : messageIdMap.entrySet()) {
+            String key = entry.getKey();
+            Long value = entry.getValue();
+            if (currentTimeMillis > value) {
+                messageIdMap.remove(key);
+            }
+        }
+        return true;
+    }
+
+    public void send(WeworkMessageListBean msg) {
+        send(msg, msg.getSocketType() == 2);
+    }
+
+    public void send(WeworkMessageListBean msg, boolean log) {
+        String json = GsonUtils.toJson(msg);
+        boolean success = socket.send(json);
+        if (log && success)
+            LogUtils.d(url, json, "通讯消息发送成功!");
+        if (!success)
+            LogUtils.e(url, json, "通讯消息发送失败!");
+    }
+
+    public void send(String msg) {
+        boolean success = socket.send(msg);
+        LogUtils.e(url, msg, (success ? "通讯消息发送成功!" : "通讯消息发送失败!"));
+    }
+
+    public void close(int code, String reason) {
+        task.cancel(true);
+        Log.e("url", "task 取消");
+        this.socket.close(code, reason);
+        Log.e(url, "链接关闭");
+    }
+
+    public static void closeManager() {
+        Log.e("SocketManager", "关闭Manager:");
+        for (Map.Entry<String, WebSocketManager> e : webSocketManager.entrySet()) {
+            e.getValue().close(1000, "closeAll");
+        }
+        webSocketManager.clear();
+        scheduledExecutorService.shutdown();
+    }
+
+    public void reConnect() {
+        connecting = true;
+        Log.e(url, "重连");
+        boolean isConnect = false;
+        int interval = reconnectInt;
+        while (true) {
+            try {
+                isConnect = connect();
+                if (isConnect) {
+                    connecting = false;
+                    break;
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            try {
+                Thread.sleep(interval);
+                if (interval < 600000) {
+                    interval *= 2;
+                }
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private boolean connect() {
+        WebSocket s = new OkHttpClient().newWebSocket(new Request.Builder().url(url).build(), listener);
+        if (s.send(WebSocketManager.HEARTBEAT)) {
+            this.socket = s;
+            s.send("{\"td\":" + System.currentTimeMillis() + "}");
+            return true;
+        }
+        return false;
+    }
+
+    private ScheduledFuture heartCheckStart() {
+        lastConnectedTime = System.currentTimeMillis();
+        Runnable r = () -> {
+            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
+            Log.d(url, "心跳检测" + df.format(new Date()));// new Date()为获取当前系统时间
+            if (!connecting && (socket == null || !socket.send(HEARTBEAT))) {
+                //断开链接后关闭新消息接收
+                WeworkController.INSTANCE.setEnableLoopRunning(false);
+                //断开链接后进入重连
+                reConnect();
+                //重连后刷新连接时间
+                lastConnectedTime = System.currentTimeMillis();
+            } else if (System.currentTimeMillis() % 1000 == 0) {
+                socket.send("{\"td\":" + System.currentTimeMillis() + "}");
+            }
+            if (!Constant.INSTANCE.getEnableMediaProject()) {
+                if (System.currentTimeMillis() - lastConnectedTime > heartBeatRate * 3000 && !FloatWindowHelper.INSTANCE.isPause()) {
+                    ToastUtils.show("机器人运行中 请勿人工操作手机~");
+                }
+            }
+        };
+
+        //每heartBeatRate秒发一次心跳包
+        return scheduledExecutorService.scheduleAtFixedRate(r, heartBeatRate, heartBeatRate, TimeUnit.SECONDS);
+    }
+
+    public static WebSocketManager getWebSocketManager(String id) {
+        return webSocketManager.get(id);
+    }
+}

+ 434 - 0
app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt

@@ -0,0 +1,434 @@
+package org.yameida.worktool.utils
+
+import android.view.accessibility.AccessibilityNodeInfo
+import com.blankj.utilcode.util.LogUtils
+import org.yameida.worktool.Constant
+import org.yameida.worktool.model.WeworkMessageBean
+import org.yameida.worktool.observer.MultiFileObserver
+import org.yameida.worktool.service.*
+import org.yameida.worktool.utils.AccessibilityUtil.findAllOnceByClazz
+import org.yameida.worktool.utils.AccessibilityUtil.findFrontNode
+import org.yameida.worktool.utils.AccessibilityUtil.findOneByClazz
+
+/**
+ * 房间特征分析工具类
+ */
+object WeworkRoomUtil {
+
+    /**
+     * 房间类型 ROOM_TYPE
+     * @see WeworkMessageBean.ROOM_TYPE_UNKNOWN
+     * @see WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP
+     * @see WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT
+     * @see WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP
+     * @see WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT
+     */
+    fun getRoomType(print: Boolean = true): Int {
+        val roomTitle = getRoomTitle(noCut = true, print = false)
+        when {
+            isExternalSingleChat(roomTitle) -> {
+                LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_CONTACT")
+                return WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT
+            }
+            isExternalGroup() -> {
+                LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_GROUP")
+                return WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP
+            }
+            isGroupChat(roomTitle) -> {
+                LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_GROUP")
+                return WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP
+            }
+            isSingleChat() -> {
+                LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_CONTACT")
+                return WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT
+            }
+            else -> {
+                if (print) LogUtils.d("ROOM_TYPE: ROOM_TYPE_UNKNOWN")
+                return WeworkMessageBean.ROOM_TYPE_UNKNOWN
+            }
+        }
+    }
+
+    /**
+     * 房间标题
+     */
+    fun getRoomTitle(print: Boolean = true, noCut: Boolean = false): ArrayList<String> {
+        val titleList = arrayListOf<String>()
+        //聊天消息列表 1ListView 0RecycleView xViewGroup
+        val list = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ListView)
+        if (list != null) {
+            val frontNode = findFrontNode(list.parent?.parent)
+            val textViewList = findAllOnceByClazz(frontNode, Views.TextView)
+            for (textView in textViewList) {
+                if (!textView.text.isNullOrBlank()) {
+                    val text = textView.text.toString()
+                    titleList.add(text.replace(Constant.digitalRegex, ""))
+                    if (noCut && text.contains(Constant.digitalRegex)) {
+                        titleList.add(text)
+                    }
+                }
+            }
+        }
+        if (print) LogUtils.v("getRoomTitle: ", titleList)
+        return titleList
+    }
+
+    /**
+     * 进入房间(单聊或群聊)
+     */
+    fun intoRoom(title: String, fastIn: Boolean = true): Boolean {
+        if (checkRoom(title, strict = true)) {
+            return true
+        }
+        goHome()
+        val list = findOneByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup)
+        if (fastIn) {
+            if (list != null && list.childCount >= 2) {
+                for (i in 0 until list.childCount) {
+                    val item = list.getChild(i)
+                    val tvList = findAllOnceByClazz(item, Views.TextView).mapNotNull { it.text }
+                    if (tvList.isNotEmpty() && title == tvList[0].toString()) {
+                        intoRoomPreInit()
+                        AccessibilityUtil.performClick(item)
+                        LogUtils.d("快捷进入房间: $title")
+                        AccessibilityUtil.waitForPageMissing(
+                            "WwMainActivity",
+                            "GlobalSearchActivity"
+                        )
+                        AccessibilityExtraUtil.loadingPage(
+                            "ExternalGroupMessageListActivity",
+                            "ExternalWechatUserMessageListActivity",
+                            "MessageListActivity",
+                            timeout = Constant.CHANGE_PAGE_INTERVAL
+                        )
+                        return checkRoom(title, strict = false)
+                    }
+                }
+            }
+        }
+        if (list != null) {
+            val frontNode = findFrontNode(list)
+            val textViewList = findAllOnceByClazz(frontNode, Views.TextView)
+            if (textViewList.size >= 2) {
+                val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2]
+                val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1]
+                AccessibilityUtil.performClick(searchButton)
+                val needTrim = title.contains(Constant.regTrimTitle)
+                val trimTitle = title.replace(Constant.regTrimTitle, "")
+                AccessibilityUtil.findTextInput(getRoot(), trimTitle)
+                if (!AccessibilityExtraUtil.loadingPage("GlobalSearchActivityV2")) {
+                    LogUtils.e("未找到搜索页")
+                    return false
+                }
+                //消息页搜索结果列表
+                val selectListView = findOneByClazz(getRoot(), Views.RecyclerView)
+                val reverseRegexTitle = RegexHelper.reverseRegexTitle(trimTitle)
+                val regex1 =
+                    (if (Constant.friendRemarkStrict) "^$reverseRegexTitle" else "^(微信昵称:)|((企业)?邮箱:)?$reverseRegexTitle") +
+                            (if (needTrim) ".*?" else Constant.suffixString)
+                val regex2 = ".*?\\($reverseRegexTitle\\)$"
+                val regex = "($regex1)|($regex2)"
+                val searchResult = AccessibilityUtil.findAllByTextRegex(
+                    selectListView,
+                    regex,
+                    timeout = Constant.CHANGE_PAGE_INTERVAL * 3,
+                    root = false
+                )
+                if (searchResult.isNotEmpty()) {
+                    //过滤已退出的群聊
+                    val searchItem = searchResult.firstOrNull {
+                        it.parent != null && it.parent.childCount < 3
+                    }
+                    if (searchItem != null) {
+                        intoRoomPreInit()
+                        AccessibilityUtil.performClick(searchItem)
+                        val text =
+                            AccessibilityUtil.findAllOnceByClazz(searchItem.parent, Views.TextView)
+                                .firstOrNull { it.text != null && !it.text.isNullOrBlank() }
+                        val title = text?.text?.toString() ?: title
+                        LogUtils.d("进入房间: $title")
+                        AccessibilityUtil.waitForPageMissing(
+                            "WwMainActivity",
+                            "GlobalSearchActivityV2"
+                        )
+                        AccessibilityExtraUtil.loadingPage(
+                            "ExternalGroupMessageListActivity",
+                            "ExternalWechatUserMessageListActivity",
+                            "MessageListActivity",
+                            timeout = Constant.CHANGE_PAGE_INTERVAL
+                        )
+                        return checkRoom(title, strict = false)
+                    } else {
+                        LogUtils.e("搜索到已退出群聊")
+                    }
+                } else {
+                    LogUtils.e("未搜索到结果")
+                    val noResult = AccessibilityUtil.findOnceByText(
+                        getRoot(),
+                        "无搜索结果",
+                        "暂无搜索结果",
+                        exact = true
+                    ) != null
+                    LogUtils.e("企微: 无搜索结果: $noResult")
+                }
+            } else {
+                LogUtils.e("未找到搜索按钮")
+            }
+        } else {
+            LogUtils.e("未找到聊天列表")
+        }
+        return false
+    }
+
+    /**
+     * 进入群管理页
+     * @return true 成功进入群管理页
+     */
+    fun intoGroupManager(): Boolean {
+        if (AccessibilityUtil.findOneByText(
+                getRoot(),
+                "全部群成员",
+                "微信用户创建",
+                timeout = Constant.CHANGE_PAGE_INTERVAL
+            ) != null
+        ) {
+            return true
+        }
+        //群详情列表
+        val list = findOneByClazz(getRoot(), Views.ListView)
+        if (list != null) {
+            val frontNode = AccessibilityUtil.findFrontNode(list.parent?.parent)
+            val textViewList = findAllOnceByClazz(frontNode, Views.TextView)
+            if (textViewList.size >= 2) {
+                val multiButton = textViewList.lastOrNull()
+                AccessibilityUtil.performClick(multiButton)
+                sleep(Constant.CHANGE_PAGE_INTERVAL)
+                return true
+            } else {
+                LogUtils.e("未找到群管理按钮")
+            }
+        }
+        return false
+    }
+
+    fun intoGroupHarass(): Boolean {
+        val harass = AccessibilityUtil.scrollAndFindByText(
+            WeworkController.weworkService,
+            getRoot(),
+            "防骚扰",
+            exact = true
+        )
+        if (harass != null) {
+            AccessibilityUtil.performClick(harass)
+            return true
+        }
+        return false
+    }
+
+    /**
+     * 进入好友详情页
+     * @return true 成功进入好友详情页
+     */
+    fun intoFriendDetail(): Boolean {
+        if (AccessibilityUtil.findOneByText(
+                getRoot(),
+                "设置聊天背景",
+                timeout = Constant.CHANGE_PAGE_INTERVAL
+            ) != null
+        ) {
+            return true
+        }
+        //同群详情列表
+        val list = findOneByClazz(getRoot(), Views.ListView)
+        if (list != null) {
+            val frontNode = AccessibilityUtil.findFrontNode(list.parent?.parent)
+            val textViewList = findAllOnceByClazz(frontNode, Views.TextView)
+            if (textViewList.size >= 2) {
+                val multiButton = textViewList.lastOrNull()
+                AccessibilityUtil.performClick(multiButton)
+                return true
+            } else {
+                LogUtils.e("未找到好友详情按钮")
+            }
+        }
+        return false
+    }
+
+    /**
+     * 获取当前聊天人姓名并返回房间
+     * 解决title为对方正在输入中问题
+     * @return name 单聊对方姓名
+     */
+    fun getFriendName(): ArrayList<String> {
+        val titleList = arrayListOf<String>()
+        if (intoFriendDetail()) {
+            if (AccessibilityUtil.findOneByText(
+                    getRoot(),
+                    "设置聊天背景",
+                    timeout = Constant.CHANGE_PAGE_INTERVAL
+                ) != null
+            ) {
+                LogUtils.e("获取好友名称失败 校验未进入好友详情页")
+                return titleList
+            }
+            val gridView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.GridView)
+            if (gridView != null && gridView.childCount >= 2) {
+                LogUtils.i("获取好友名称 使用GridView")
+                val tvList = findAllOnceByClazz(gridView.getChild(0), Views.TextView)
+                for (textView in tvList) {
+                    if (textView.text != null) {
+                        titleList.add(textView.text.toString())
+                    }
+                }
+            } else {
+                LogUtils.i("获取好友名称 使用RecyclerView")
+                val recyclerViewList =
+                    AccessibilityUtil.findAllOnceByClazz(getRoot(), Views.RecyclerView)
+                if (recyclerViewList.size >= 2 && recyclerViewList[1].childCount >= 2) {
+                    val rvList = recyclerViewList[1]
+                    val tvList = findAllOnceByClazz(rvList.getChild(0), Views.TextView)
+                    for (textView in tvList) {
+                        if (textView.text != null) {
+                            titleList.add(textView.text.toString())
+                        }
+                    }
+                }
+            }
+            backPress()
+            LogUtils.d("获取好友名称成功 ${titleList.joinToString()}")
+        } else {
+            LogUtils.e("获取好友名称失败")
+        }
+        return titleList
+    }
+
+    /**
+     * 获取当前群名并返回房间
+     * 如果有群备注则列表先插入群备注
+     * @return name 单聊对方姓名
+     */
+    fun getFullGroupTitle(): ArrayList<String> {
+        val titleList = arrayListOf<String>()
+        if (intoGroupManager()) {
+            val groupInfo = WeworkGetImpl.getGroupInfoDetail()
+            if (!groupInfo.groupRemark.isNullOrEmpty()) {
+                titleList.add(groupInfo.groupRemark)
+            }
+            titleList.add(groupInfo.groupName)
+        }
+        return titleList
+    }
+
+    /**
+     * 群名是否存在
+     */
+    fun isGroupExists(groupName: String): Boolean {
+        return intoRoom(groupName)
+    }
+
+    /**
+     * 检查当前房间
+     */
+    private fun checkRoom(title: String, strict: Boolean = false): Boolean {
+        LogUtils.d("checkRoom(): $title strict: $strict")
+        var titleList = getRoomTitle(print = false)
+        if (titleList.isEmpty()) {
+            return false
+        }
+        val roomType = getRoomType()
+        if (strict && titleList.count { it.endsWith("…") } > 0) {
+            LogUtils.d("title too long... try get full name titleList: ${titleList.joinToString()}")
+            if (roomType == WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT || roomType == WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT) {
+                titleList = getFriendName()
+            } else if (Constant.fullGroupName
+                && (roomType == WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP || roomType == WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP)
+            ) {
+                titleList = getFullGroupTitle()
+            }
+        }
+        val suffixRegex =
+            if (roomType == WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP || roomType == WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP) {
+                Constant.groupSuffixRegex
+            } else {
+                Constant.suffixRegex
+            }
+        val dealTitle = title.replace(suffixRegex, "")
+        LogUtils.d("dealTitle: $dealTitle", "titleList: ${titleList.joinToString()}")
+        if (roomType != WeworkMessageBean.ROOM_TYPE_UNKNOWN
+            && (titleList.count { dealTitle == it.replace(suffixRegex, "") } > 0
+                    || ((!strict || !Constant.fullGroupName) && titleList.count {
+                dealTitle.contains(
+                    it.replace(suffixRegex, "")
+                )
+            } > 0))
+        ) {
+            intoRoomPreInit()
+            LogUtils.d("当前正在房间")
+            return true
+        }
+        return false
+    }
+
+    /**
+     * 是否是群聊
+     * 群名最后有(\d)显示群人数
+     */
+    private fun isGroupChat(roomTitle: ArrayList<String>): Boolean {
+        return roomTitle.size > 1 && roomTitle[1].contains(Constant.digitalRegex)
+    }
+
+    /**
+     * 是否是外部群
+     * listview前兄弟控件 && text包含外部群
+     */
+    private fun isExternalGroup(): Boolean {
+        //聊天消息列表 1ListView 0RecycleView xViewGroup
+        val listView = AccessibilityUtil.findOnceByClazz(
+            getRoot(),
+            Views.ListView,
+            limitDepth = null,
+            depth = 0
+        )
+        if (listView != null) {
+            val frontNode = findFrontNode(listView)
+            if (frontNode != null) {
+                val nodeList = AccessibilityUtil.findAllOnceByText(frontNode, "外部群")
+                return nodeList.isNotEmpty()
+            }
+        }
+        return false
+    }
+
+    /**
+     * 是否是单聊
+     * 有列表和输入框
+     */
+    private fun isSingleChat(): Boolean {
+        //聊天消息列表 1ListView 0RecycleView xViewGroup
+        val list = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ListView)
+        val editText = AccessibilityUtil.findOnceByClazz(getRoot(), Views.EditText)
+            ?: AccessibilityUtil.findOnceByText(getRoot(), "按住 说话", "按住说话", exact = true)
+        if (list != null && editText != null) {
+            return true
+        }
+        return false
+    }
+
+    /**
+     * 是否是外部单聊
+     * 姓名下面有@xx
+     */
+    private fun isExternalSingleChat(roomTitle: ArrayList<String>): Boolean {
+        return roomTitle.size > 1 && roomTitle.count { it.matches("^[@@].*?".toRegex()) } > 0
+    }
+
+    /**
+     * 初始化进入房间前的事件
+     */
+    private fun intoRoomPreInit(): Boolean {
+        MultiFileObserver.createSet.clear()
+        MultiFileObserver.finishSet.clear()
+        return true
+    }
+
+}

+ 556 - 0
app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt

@@ -0,0 +1,556 @@
+package org.yameida.worktool.utils
+
+import android.view.accessibility.AccessibilityNodeInfo
+import com.blankj.utilcode.util.LogUtils
+import org.yameida.worktool.Constant
+import org.yameida.worktool.model.WeworkMessageBean
+import org.yameida.worktool.service.WeworkController
+import org.yameida.worktool.service.getRoot
+import org.yameida.worktool.service.sleep
+import org.yameida.worktool.utils.AccessibilityUtil.findAllByClazz
+import org.yameida.worktool.utils.AccessibilityUtil.findAllOnceByClazz
+
+/**
+ * 消息特征分析工具类
+ */
+object WeworkTextUtil {
+
+    /*
+文字 1tv 0iv (文字)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.RelativeLayout
+------ depth: 2 className: android.widget.LinearLayout
+--------- depth: 3 className: android.widget.LinearLayout
+------------ depth: 4 className: android.widget.FrameLayout
+--------------- depth: 5 className: android.widget.TextView
+
+图片 0tv 1iv (图片)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.RelativeLayout
+------ depth: 2 className: android.widget.ImageView
+
+视频 2tv 2iv (视频大小、视频时长、缩略图、播放按钮)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.RelativeLayout
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.ImageView
+------------ depth: 4 className: android.widget.ImageView
+------------ depth: 4 className: android.view.View
+------------ depth: 4 className: android.widget.TextView
+------------ depth: 4 className: android.widget.TextView
+
+腾讯文档 2tv 2iv (标题、创建者、图标、缩略图) (需要和视频区分 视频子节点数>3)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.LinearLayout
+------ depth: 2 className: android.widget.LinearLayout
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.RelativeLayout
+--------------- depth: 5 className: android.widget.LinearLayout
+------------------ depth: 6 className: android.widget.LinearLayout
+--------------------- depth: 7 className: android.widget.TextView
+--------------------- depth: 7 className: android.widget.TextView
+------------------ depth: 6 className: android.widget.ImageView
+--------------- depth: 5 className: android.widget.ImageView
+
+链接 3tv 1iv (标题、副标题、下方来源、图标)
+发送纯文本但含链接被识别为网页 (纯文本、网页title、网址域名、图标)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.LinearLayout
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.TextView
+------------ depth: 4 className: android.widget.RelativeLayout
+--------------- depth: 5 className: android.widget.LinearLayout
+------------------ depth: 6 className: android.widget.TextView
+--------------- depth: 5 className: android.widget.ImageView
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.view.View
+--------- depth: 3 className: android.widget.TextView
+
+文件 3tv 1iv (文件名、文件大小、下方来源、图标) (需要和链接区分 文件大小特征匹配)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.LinearLayout
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.RelativeLayout
+--------------- depth: 5 className: android.widget.TextView
+--------------- depth: 5 className: android.widget.TextView
+------------ depth: 4 className: android.widget.ImageView
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.view.View
+--------- depth: 3 className: android.widget.TextView
+
+小程序 3tv 2iv (标题、副标题、下方来源、小程序icon、图标)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.LinearLayout
+------ depth: 2 className: android.widget.LinearLayout
+--------- depth: 3 className: android.widget.LinearLayout
+------------ depth: 4 className: android.widget.ImageView
+------------ depth: 4 className: android.widget.TextView
+--------- depth: 3 className: android.widget.TextView
+--------- depth: 3 className: android.widget.ImageView
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.view.View
+--------- depth: 3 className: android.widget.TextView
+
+合并聊天记录 2tv 0iv (标题、摘要)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.LinearLayout
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.TextView
+------------ depth: 4 className: android.widget.TextView
+
+收集表 6tv 0iv (标题、副标题、行1、行2、行3、下方来源)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.LinearLayout
+------ depth: 2 className: android.widget.TextView
+------ depth: 2 className: android.widget.TextView
+------ depth: 2 className: android.widget.LinearLayout
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.TextView
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.TextView
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.TextView
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.view.View
+--------- depth: 3 className: android.widget.TextView
+
+接龙 2tv 1iv (内容、接龙标识、跳转按钮)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.RelativeLayout
+------ depth: 2 className: android.widget.LinearLayout
+--------- depth: 3 className: android.widget.LinearLayout
+------------ depth: 4 className: android.widget.TextView
+--------- depth: 3 className: android.widget.LinearLayout
+------------ depth: 4 className: android.widget.TextView
+------------ depth: 4 className: android.widget.ImageView
+
+语音 4tv 2iv (空、语音时长、转写文字、转写状态、语音图片、转写图标)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.LinearLayout
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.TextView
+------------ depth: 4 className: android.widget.RelativeLayout
+--------------- depth: 5 className: android.widget.RelativeLayout
+------------------ depth: 6 className: android.widget.ImageView
+--------------- depth: 5 className: android.widget.ImageView
+------------ depth: 4 className: android.widget.TextView
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.widget.TextView
+--------- depth: 3 className: android.widget.TextView
+
+名片 5tv 1iv (机构名、姓名、别名、职务、下方来源、头像)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.RelativeLayout
+------ depth: 2 className: android.widget.LinearLayout
+--------- depth: 3 className: android.widget.TextView
+--------- depth: 3 className: android.widget.LinearLayout
+------------ depth: 4 className: android.widget.TextView
+--------- depth: 3 className: android.widget.TextView
+--------- depth: 3 className: android.widget.TextView
+------ depth: 2 className: android.widget.ImageView
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.view.View
+--------- depth: 3 className: android.widget.TextView
+
+位置 1tv 2iv (地址、位置、定位柄)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.RelativeLayout
+------ depth: 2 className: android.widget.RelativeLayout
+--------- depth: 3 className: android.widget.FrameLayout
+------------ depth: 4 className: android.widget.ImageView
+--------- depth: 3 className: android.widget.TextView
+--------- depth: 3 className: android.widget.ImageView
+
+带回复引用文本 3tv 0iv (引用发言人、引用发言内容、本次消息内容)
+ depth: 0 className: android.widget.RelativeLayout
+--- depth: 1 className: android.widget.RelativeLayout
+------ depth: 2 className: android.widget.LinearLayout
+--------- depth: 3 className: android.widget.RelativeLayout
+------------ depth: 4 className: android.widget.LinearLayout
+--------------- depth: 5 className: android.widget.RelativeLayout
+------------------ depth: 6 className: android.view.View
+------------------ depth: 6 className: android.widget.RelativeLayout
+--------------------- depth: 7 className: android.widget.TextView
+--------------------- depth: 7 className: android.widget.LinearLayout
+------------------------ depth: 8 className: android.widget.RelativeLayout
+--------------------------- depth: 9 className: android.widget.RelativeLayout
+------------------------------ depth: 10 className: android.widget.TextView
+--------------- depth: 5 className: android.widget.TextView
+
+------------------------------总结------------------------------
+图片 0tv 1iv (图片)
+视频 2tv 2iv (视频大小、视频时长、缩略图、播放按钮)
+链接 3tv 1iv (标题、副标题、下方来源、图标)
+文件 3tv 1iv (文件名、文件大小、下方来源、图标) (需要和链接区分)
+链接 2tv 1iv (文件名、副标题、图标) (微信*用户发的链接不带下方来源 需要和接龙和链接区分)
+链接 1tv 1iv (文件名、图标) (微信*用户发的链接不带副标题和下方来源)
+文件 2tv 1iv (文件名、文件大小、图标) (微信*用户发的文件不带下方来源 需要和接龙和链接区分)
+小程序 3tv 2iv (标题、副标题、下方来源、小程序icon、图标)
+合并聊天记录 2tv 0iv (标题、摘要)
+收集表 6tv 0iv (标题、副标题、行1、行2、行3、下方来源)
+接龙 2tv 1iv (内容、接龙标识、跳转按钮)
+语音 4tv 2iv (空、语音时长、转写文字、转写状态、语音图片、转写图标)
+名片 5tv 1iv (机构名、姓名、别名、职务、下方来源、头像)
+位置 1tv 2iv (地址、位置、定位柄)
+带回复引用文本 1tv 2iv (引用发言人、引用发言内容、本次消息内容)
+     */
+
+    /**
+     * 企微消息类型 TEXT_TYPE
+     * @see WeworkMessageBean.TEXT_TYPE
+     */
+    fun getTextType(node: AccessibilityNodeInfo?, isGroup: Boolean = true): Int {
+        if (node == null) return WeworkMessageBean.TEXT_TYPE_UNKNOWN
+        val startTime = System.currentTimeMillis()
+        while (System.currentTimeMillis() - startTime <= Constant.LONG_INTERVAL) {
+            node.refresh()
+            AccessibilityUtil.findOnceByClazz(node, Views.ProgressBar) ?: break
+            LogUtils.e("发现加载项 等待加载完成...")
+            sleep(Constant.POP_WINDOW_INTERVAL / 5)
+        }
+        val tvList = findAllOnceByClazz(node, Views.TextView)
+        val tvCount = tvList.size
+        val ivCount = findAllOnceByClazz(node, Views.ImageView).size
+        LogUtils.v("tvCount: $tvCount ivCount: $ivCount")
+        return when {
+            tvCount == 1 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_PLAIN
+            tvCount == 1 && ivCount == 1 -> WeworkMessageBean.TEXT_TYPE_LINK
+            tvCount == 0 && ivCount == 1 -> WeworkMessageBean.TEXT_TYPE_IMAGE
+            tvCount == 2 && ivCount == 2 -> {
+                val parent = tvList[0].parent
+                if ((tvList[0].text?.toString() ?: "").matches("直播中".toRegex())) {
+                    WeworkMessageBean.TEXT_TYPE_CHANNELS_LIVE
+                } else if (parent != null && parent.childCount > 3) {
+                    WeworkMessageBean.TEXT_TYPE_VIDEO
+                } else {
+                    WeworkMessageBean.TEXT_TYPE_OFFICE
+                }
+            }
+            tvCount == 3 && ivCount == 1 -> {
+                if (isFileSize(tvList[1].text?.toString())) {
+                    WeworkMessageBean.TEXT_TYPE_FILE
+                } else {
+                    WeworkMessageBean.TEXT_TYPE_LINK
+                }
+            }
+            tvCount == 3 && ivCount == 2 -> WeworkMessageBean.TEXT_TYPE_MICROPROGRAM
+            tvCount == 2 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_CHAT_RECORD
+            tvCount == 6 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_COLLECTION
+            tvCount == 2 && ivCount == 1 -> {
+                if (isSolitaire(tvList[1].text?.toString())) {
+                    WeworkMessageBean.TEXT_TYPE_SOLITAIRE
+                } else if (isFileSize(tvList[1].text?.toString())) {
+                    WeworkMessageBean.TEXT_TYPE_FILE
+                } else {
+                    WeworkMessageBean.TEXT_TYPE_LINK
+                }
+            }
+            tvCount == 4 && ivCount == 2 -> WeworkMessageBean.TEXT_TYPE_VOICE
+            tvCount == 5 && ivCount == 1 -> WeworkMessageBean.TEXT_TYPE_CARD
+            tvCount == 1 && ivCount == 2 -> {
+                if ((tvList[0].text?.toString() ?: "").matches("[0-9]+:[0-9]+".toRegex()))
+                    WeworkMessageBean.TEXT_TYPE_VIDEO
+                else
+                    WeworkMessageBean.TEXT_TYPE_LOCATION
+            }
+            tvCount == 3 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_REPLY
+            tvCount == 0 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_NOTIFY_ROBOT
+            tvCount == 1 && ivCount == 3 -> WeworkMessageBean.TEXT_TYPE_CHANNELS_VIDEO
+            else -> WeworkMessageBean.TEXT_TYPE_UNKNOWN
+        }
+    }
+
+    /**
+     * 企微消息类型 TEXT_TYPE
+     * @see WeworkMessageBean.TEXT_TYPE
+     */
+    fun getTextTypeFromItem(node: AccessibilityNodeInfo?, isGroup: Boolean = true): Int {
+        if (node == null) return WeworkMessageBean.TEXT_TYPE_UNKNOWN
+        //消息主体
+        val relativeLayoutItem = AccessibilityUtil.findOnceByClazz(node, Views.RelativeLayout, limitDepth = 1)
+        if (relativeLayoutItem != null && relativeLayoutItem.childCount >= 2) {
+            if (Views.ImageView.equals(relativeLayoutItem.getChild(relativeLayoutItem.childCount - 2).className)) {
+                LogUtils.v("头像在左边 本条消息发送者为其他联系人")
+                var textType = WeworkMessageBean.TEXT_TYPE_UNKNOWN
+                val relativeLayoutContent =
+                    AccessibilityUtil.findOnceByClazz(relativeLayoutItem, Views.RelativeLayout, limitDepth = 2)
+                if (relativeLayoutContent != null) {
+                    textType = getTextType(relativeLayoutContent)
+                    LogUtils.v("textType: $textType")
+                    return textType
+                }
+            } else if (Views.ImageView.equals(relativeLayoutItem.getChild(relativeLayoutItem.childCount - 1).className)) {
+                LogUtils.v("头像在右边 本条消息发送者为自己")
+                var textType = WeworkMessageBean.TEXT_TYPE_UNKNOWN
+                val subLayout = relativeLayoutItem.getChild(relativeLayoutItem.childCount - 2)
+                if (subLayout.childCount > 0) {
+                    textType = WeworkTextUtil.getTextType(subLayout)
+                    LogUtils.v("textType: $textType")
+                    return textType
+                }
+            }
+        }
+        return WeworkMessageBean.TEXT_TYPE_UNKNOWN
+    }
+
+    /**
+     * 企微消息 发送者
+     * sender 0其他人 1机器人自己 2unknown(如系统消息)
+     */
+    private fun getSender(node: AccessibilityNodeInfo?, isGroup: Boolean = true): Int {
+        if (node == null) return WeworkMessageBean.TEXT_TYPE_UNKNOWN
+        //消息主体
+        val relativeLayoutItem = AccessibilityUtil.findOnceByClazz(node, Views.RelativeLayout, limitDepth = 1)
+        if (relativeLayoutItem != null && relativeLayoutItem.childCount >= 2) {
+            if (Views.ImageView.equals(relativeLayoutItem.getChild(0).className)) {
+                return 0
+            } else if (Views.ImageView.equals(relativeLayoutItem.getChild(1).className)) {
+                return 1
+            }
+        }
+        return 2
+    }
+
+    /**
+     * 是否为消息上方时间
+     */
+    fun isDate(date: String): Boolean {
+        return date.matches(".*?([上下]午)[\\s ]+?[0-9]+:[0-9]+".toRegex())
+    }
+
+    /**
+     * 是否为文件上方时间
+     */
+    fun isFileSize(size: String?): Boolean {
+        return size?.matches("[0-9\\.]+[BKMG]".toRegex()) ?: false
+    }
+
+    /**
+     * 是否为接龙
+     */
+    fun isSolitaire(text: String?): Boolean {
+        return text?.contains("参与接龙") ?: false
+    }
+
+    /**
+     * 群聊 提取发言人昵称
+     * 适用于左侧发言者
+     * @param item 消息item节点
+     */
+    fun getNameList(item: AccessibilityNodeInfo): List<String> {
+        val nameList = ArrayList<String>()
+        val node = AccessibilityUtil.findOnceByClazz(item, Views.ViewGroup)
+        if (node != null) {
+            val textViewList = findAllOnceByClazz(node, Views.TextView)
+            for (textView in textViewList) {
+                if (textView.text != null) {
+                    nameList.add(textView.text.toString())
+                }
+            }
+        }
+        return nameList
+    }
+
+    /**
+     * 长按消息条目
+     * 复制、转发、回复、收藏、置顶、多选、日程、待办、翻译、删除
+     * 适用左侧发言者
+     * @param node 消息列表节点
+     * @param replyTextType 带回复消息类型
+     * @param replyNick 待回复人姓名
+     * @param replyContent 待回复内容
+     * @param key 复制、转发、回复、收藏、多选
+     * @return true 进行了长按 否则 false
+     */
+    fun longClickMessageItem(
+        node: AccessibilityNodeInfo?,
+        replyTextType: Int,
+        replyNick: String?,
+        replyContent: String,
+        vararg key: String
+    ): Boolean {
+        if (node == null) return false
+        for (i in 0 until node.childCount) {
+            val item = node.getChild(node.childCount - 1 - i) ?: continue
+            val nameList = getNameList(item)
+            if (nameList.isEmpty()) {
+                val backNode = getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT)
+                if (backNode != null) {
+                    val textTypeFromItem = getTextTypeFromItem(item)
+                    val sender = getSender(item)
+                    if ((replyNick != null && sender == 1) || (replyNick == null && sender == 0)) {
+                        continue
+                    }
+                    if ((replyTextType == WeworkMessageBean.TEXT_TYPE_IMAGE)
+                        && (replyTextType == textTypeFromItem)) {
+                        LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
+                        return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, *key)
+                    }
+                    if ((replyTextType == WeworkMessageBean.TEXT_TYPE_FILE || replyTextType == WeworkMessageBean.TEXT_TYPE_VIDEO)
+                        && replyContent.contains("###")) {
+                        val replyContentList = replyContent.split("###")
+                        if (AccessibilityUtil.findOnceByText(backNode, replyContentList[0]) != null
+                            && AccessibilityUtil.findOnceByText(backNode, replyContentList[1]) != null) {
+                            LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
+                            return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, *key)
+                        }
+                    }
+                    val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent)
+                    if (textNode != null && replyContent.isNotEmpty()) {
+                        LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
+                        return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, *key)
+                    }
+                }
+            }
+            for (name in nameList) {
+                if (name == replyNick) {
+                    val backNode = getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP)
+                    if (backNode != null) {
+                        val textTypeFromItem = getTextTypeFromItem(item)
+                        if ((replyTextType == WeworkMessageBean.TEXT_TYPE_IMAGE)
+                            && (replyTextType == textTypeFromItem)) {
+                            LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
+                            return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, *key)
+                        }
+                        if ((replyTextType == WeworkMessageBean.TEXT_TYPE_FILE || replyTextType == WeworkMessageBean.TEXT_TYPE_VIDEO)
+                            && replyContent.contains("###")) {
+                            val replyContentList = replyContent.split("###")
+                            if (AccessibilityUtil.findOnceByText(backNode, replyContentList[0]) != null
+                                && AccessibilityUtil.findOnceByText(backNode, replyContentList[1]) != null) {
+                                LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
+                                return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, *key)
+                            }
+                        }
+                        val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent)
+                        if (textNode != null && replyContent.isNotEmpty()) {
+                            LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
+                            return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, *key)
+                        }
+                    }
+                }
+            }
+        }
+        return false
+    }
+
+    /**
+     * 长按消息条目
+     * 复制、转发、回复、收藏、置顶、多选、日程、待办、翻译、删除、撤回
+     * 适用自己发言者
+     * @param node 消息列表节点
+     * @param replyTextType 带回复消息类型
+     * @param replyContent 待回复内容
+     * @param key 复制、转发、回复、收藏、多选
+     * @return true 进行了长按 否则 false
+     */
+    fun longClickMyMessageItem(
+        node: AccessibilityNodeInfo?,
+        replyTextType: Int,
+        replyContent: String,
+        key: String
+    ): Boolean {
+        if (node == null) return false
+        for (i in 0 until node.childCount) {
+            val item = node.getChild(node.childCount - 1 - i) ?: continue
+            val frontNode = getMyMessageListNode(item)
+            if (frontNode != null) {
+                val textType = getTextTypeFromItem(item)
+                if (replyTextType == WeworkMessageBean.TEXT_TYPE_UNKNOWN || replyTextType == textType) {
+                    if (replyTextType == WeworkMessageBean.TEXT_TYPE_IMAGE) {
+                        return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, key)
+                    }
+                    if ((replyTextType == WeworkMessageBean.TEXT_TYPE_FILE || replyTextType == WeworkMessageBean.TEXT_TYPE_VIDEO)
+                        && replyContent.contains("###")) {
+                        val replyContentList = replyContent.split("###")
+                        if (AccessibilityUtil.findOnceByText(frontNode, replyContentList[0]) != null
+                            && AccessibilityUtil.findOnceByText(frontNode, replyContentList[1]) != null) {
+                            return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, key)
+                        }
+                    }
+                    val textNode = AccessibilityUtil.findOnceByText(frontNode, replyContent, exact = true)
+                    if (textNode != null && replyContent.isNotEmpty()) {
+                        return longClickMyMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, key)
+                    }
+                }
+            }
+        }
+        return false
+    }
+
+    private fun longClickMessageItem(item: AccessibilityNodeInfo, roomType: Int, vararg key: String): Boolean {
+        val backNode = getMessageListNode(item, roomType)
+        if ("单击" in key) {
+            return AccessibilityUtil.clickByNode(WeworkController.weworkService, backNode)
+        }
+        AccessibilityUtil.performLongClickWithSon(backNode)
+        sleep(Constant.POP_WINDOW_INTERVAL)
+        val optionRvList = findAllByClazz(getRoot(), Views.RecyclerView, Views.ViewGroup)
+        for (optionRv in optionRvList) {
+            val keyTv = AccessibilityUtil.findOnceByText(optionRv, *key, exact = true)
+            if (keyTv != null) {
+                AccessibilityUtil.performClick(keyTv)
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun longClickMyMessageItem(item: AccessibilityNodeInfo, roomType: Int, key: String): Boolean {
+        val frontNode = getMyMessageListNode(item)
+        if (key == "单击") {
+            return AccessibilityUtil.clickByNode(WeworkController.weworkService, frontNode)
+        }
+        AccessibilityUtil.performLongClickWithSon(frontNode)
+        sleep(Constant.POP_WINDOW_INTERVAL)
+        val optionRvList = findAllByClazz(getRoot(), Views.RecyclerView, Views.ViewGroup)
+        for (optionRv in optionRvList) {
+            val keyTv = AccessibilityUtil.findOnceByText(optionRv, key, exact = true)
+            if (keyTv != null) {
+                AccessibilityUtil.performClick(keyTv)
+                if (AccessibilityExtraUtil.loadingPage("CustomDialog", timeout = Constant.POP_WINDOW_INTERVAL)) {
+                    AccessibilityUtil.findTextAndClick(getRoot(), "确定", "我知道了", exact = true)
+                }
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * 提取消息主体框节点(昵称下面的气泡框)
+     * 适用于左侧发言者
+     * @param item 消息item节点
+     */
+    private fun getMessageListNode(item: AccessibilityNodeInfo, roomType: Int): AccessibilityNodeInfo? {
+        if (roomType in arrayOf(WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT)) {
+            val node = AccessibilityUtil.findOnceByClazz(item, Views.ImageView)
+            if (node != null) {
+                return AccessibilityUtil.findBackNode(node)
+            }
+        } else if (roomType in arrayOf(WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP)) {
+            val node = AccessibilityUtil.findOnceByClazz(item, Views.ViewGroup)
+            if (node != null) {
+                return AccessibilityUtil.findBackNode(node)
+            }
+        }
+        return null
+    }
+
+    /**
+     * 提取消息主体框节点(昵称下面的气泡框)
+     * 适用于自己发言者
+     * @param item 消息item节点
+     */
+    private fun getMyMessageListNode(item: AccessibilityNodeInfo): AccessibilityNodeInfo? {
+        val node = AccessibilityUtil.findAllOnceByClazz(item, Views.ImageView).lastOrNull()
+        if (node?.parent?.getChild(0) != node) {
+            return AccessibilityUtil.findFrontNode(node)
+        }
+        return null
+    }
+}

+ 128 - 0
app/src/main/java/org/yameida/worktool/utils/capture/AndroidUtils.kt

@@ -0,0 +1,128 @@
+package org.yameida.worktool.utils.capture
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.StateListDrawable
+import android.media.MediaMetadataRetriever
+import android.os.Process
+import android.text.TextUtils
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import com.blankj.utilcode.util.LogUtils
+import com.blankj.utilcode.util.Utils
+import java.io.BufferedOutputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * Created by gallon on 2019/7/9.
+ */
+class AndroidUtils {
+
+    companion object {
+        private val TAG = AndroidUtils::class.java.simpleName
+
+        @JvmStatic
+        fun hideKeyboard(view: View?) {
+            if (view != null) {
+                val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+                if (imm.isActive) {
+                    imm.hideSoftInputFromWindow(view.windowToken, 0)
+                }
+            }
+        }
+
+        @JvmStatic
+        fun getMediaDurationTime(filePath: String): Long {
+            var durationTime: Long = 0
+            val retriever = MediaMetadataRetriever()
+            try {
+                retriever.setDataSource(filePath)
+                val v1 = retriever.extractMetadata(9)
+                if (TextUtils.isEmpty(v1)) {
+                    LogUtils.d(TAG, "Extract metadata failed.")
+                }
+                durationTime = java.lang.Long.parseLong(v1)
+            } catch (e: Exception) {
+                e.printStackTrace()
+                LogUtils.d(TAG, "exception = " + e.message)
+            }
+
+            try {
+                retriever.release()
+            } catch (e: Exception) {
+                LogUtils.d(TAG, "retriever.release() Exception = " + e.message)
+            }
+
+            return durationTime
+        }
+
+        @JvmStatic
+        fun hasPermission(context: Context, permission: String): Boolean {
+            return try {
+                context.checkPermission(permission, Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED
+            } catch (e: SecurityException) {
+                false
+            }
+
+        }
+
+        @JvmStatic
+        fun getTimeFileName(time: Long, type: String): String {
+            return try {
+                SimpleDateFormat(type).format(Date(time))
+            } catch (e: Exception) {
+                ""
+            }
+
+        }
+
+        @JvmStatic
+        fun getRunningAppProcesses(context: Context, packageName: String): Boolean {
+            val appProcesses = (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses ?: return false
+            for (appProcess in appProcesses) {
+                if (appProcess.importance == 100 && appProcess.processName == packageName) {
+                    return true
+                }
+            }
+            return false
+        }
+
+        @JvmStatic
+        fun setPressedBg(view: View, normal: Drawable?, focused: Drawable?, pressed: Drawable?) {
+            val bg = StateListDrawable()
+            val states = arrayOfNulls<IntArray>(6)
+            states[0] = intArrayOf(16842919, 16842910)
+            states[1] = intArrayOf(16842910, 16842908)
+            states[2] = intArrayOf(16842910)
+            states[3] = intArrayOf(16842908, 16842909)
+            states[4] = intArrayOf(16842909)
+            bg.addState(states[0], pressed)
+            bg.addState(states[3], focused)
+            bg.addState(states[2], normal)
+            view.background = bg
+        }
+
+        fun bitmapToFile(bitmap: Bitmap, fileName: String, path: String = "screenShot"): String {
+            val dir = File(Utils.getApp().getExternalFilesDir("capture"), path)
+            if (!dir.exists()) {
+                dir.mkdirs()
+            }
+            val myCaptureFile = File(dir, fileName)
+            if (!myCaptureFile.exists()) {
+                myCaptureFile.createNewFile()
+            }
+            val bos = BufferedOutputStream(FileOutputStream(myCaptureFile))
+            bitmap.compress(Bitmap.CompressFormat.JPEG, 75, bos)
+            bos.flush()
+            bos.close()
+            return myCaptureFile.absolutePath
+        }
+    }
+
+}

+ 92 - 0
app/src/main/java/org/yameida/worktool/utils/capture/BitmapUtil.kt

@@ -0,0 +1,92 @@
+package org.yameida.worktool.utils.capture
+
+import android.app.Activity
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Matrix
+import android.media.ExifInterface
+import android.net.Uri
+import android.provider.MediaStore
+import java.io.File
+import java.io.IOException
+
+
+/**
+ * Created by Gallon on 2019/9/1.
+ */
+object BitmapUtil {
+
+    /**
+     * 获取图片的旋转角度
+     *
+     * @param path 图片绝对路径
+     * @return 图片的旋转角度
+     */
+    fun getBitmapDegree(path: String): Int {
+        var degree = 0
+        try {
+            // 从指定路径下读取图片,并获取其EXIF信息
+            val exifInterface = ExifInterface(path)
+            // 获取图片的旋转信息
+            val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
+            when (orientation) {
+                ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90
+                ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180
+                ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270
+            }
+        } catch (e: IOException) {
+            e.printStackTrace()
+        }
+
+        return degree
+    }
+
+    /**
+     * 将图片按照指定的角度进行旋转
+     *
+     * @param bitmap 需要旋转的图片
+     * @param degree 指定的旋转角度
+     * @return 旋转后的图片
+     */
+    fun rotateBitmapByDegree(bitmap: Bitmap, degree: Float): Bitmap {
+        // 根据旋转角度,生成旋转矩阵
+        val matrix = Matrix()
+        matrix.postRotate(degree)
+        // 将原始图片按照旋转矩阵进行旋转,并得到新的图片
+        val newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
+        if (!bitmap.isRecycled) {
+            bitmap.recycle()
+        }
+        return newBitmap
+    }
+
+    /**
+     * 获取我们需要的整理过旋转角度的Uri
+     * @param activity  上下文环境
+     * @param path      路径
+     * @return          正常的Uri
+     */
+    fun getRotatedUri(activity: Activity, path: String): Uri {
+        val degree = getBitmapDegree(path)
+        if (degree != 0) {
+            val bitmap = BitmapFactory.decodeFile(path)
+            val newBitmap = rotateBitmapByDegree(bitmap, degree.toFloat())
+            return Uri.parse(MediaStore.Images.Media.insertImage(activity.contentResolver, newBitmap, null, null))
+        } else {
+            return Uri.fromFile(File(path))
+        }
+    }
+
+    /**
+     * 将图片按照指定的角度进行旋转
+     *
+     * @param path   需要旋转的图片的路径
+     * @param degree 指定的旋转角度
+     * @return 旋转后的图片
+     */
+    fun rotateBitmapByDegree(path: String, degree: Float): Bitmap {
+        val bitmap = BitmapFactory.decodeFile(path)
+        return rotateBitmapByDegree(bitmap, degree)
+    }
+
+}

+ 103 - 0
app/src/main/java/org/yameida/worktool/utils/capture/Color.java

@@ -0,0 +1,103 @@
+package org.yameida.worktool.utils.capture;
+
+public class Color {
+    private int R = 0;
+    private int G = 0;
+    private int B = 0;
+
+
+    public Color(int r, int g, int b) {
+        R = r;
+        G = g;
+        B = b;
+    }
+
+    public Color(int b, int g, int r, Boolean bgr) {
+        if (bgr) {
+            R = r;
+            G = g;
+            B = b;
+        } else {
+            R = b;
+            G = g;
+            B = r;
+        }
+    }
+
+    public Color(int color) {
+        R = android.graphics.Color.red(color);
+        G = android.graphics.Color.green(color);
+        B = android.graphics.Color.blue(color);
+    }
+
+    public Color() {
+    }
+
+    public int getR() {
+        return R;
+    }
+
+    public void setR(int r) {
+        R = r;
+    }
+
+    public int getG() {
+        return G;
+    }
+
+    public void setG(int g) {
+        G = g;
+    }
+
+    public int getB() {
+        return B;
+    }
+
+    public void setB(int b) {
+        B = b;
+    }
+
+
+    public int[] getDexRGB() {
+        return new int[]{R, G, B};
+    }
+
+    public int[] getDexBGR() {
+        return new int[]{B, G, R};
+    }
+
+
+    /**
+     * 判断2个颜色是否相同,由于图像渲染叠加,同一个icon可能每次渲染的色值不完全相同,所以判断的时候加入了误差
+     *
+     * @param color1
+     * @param color2
+     * @return
+     */
+    public static boolean isSame(Color color1, Color color2) {
+        return isSame(color1, color2, 30);
+    }
+
+    /**
+     * 判断颜色是否相同,自定义误差
+     *
+     * @param color1
+     * @param color2
+     * @param offset
+     * @return
+     */
+    public static boolean isSame(Color color1, Color color2, int offset) {
+
+        // 算法: R G B这3个通道的色值差的绝对值之和小于offset
+        return (Math.abs(color1.getR() - color2.getR()) + Math.abs(color1.getG() - color2.getG()) + Math.abs(color1.getB() - color2.getB())) <= offset;
+    }
+
+    public boolean equals(Color obj) {
+        return obj.getR() == R && obj.getG() == G && obj.getB() == B;
+    }
+
+    @Override
+    public String toString() {
+        return "" + R + "," + G + "," + B;
+    }
+}

+ 452 - 0
app/src/main/java/org/yameida/worktool/utils/capture/Image.java

@@ -0,0 +1,452 @@
+package org.yameida.worktool.utils.capture;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.util.Base64;
+
+import com.blankj.utilcode.util.LogUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+
+
+public class Image {
+
+    /**
+     * 保存图片到图库
+     * Image.saveImageToGallery(bt, getExternalFilesDir("").getAbsolutePath() + "/asdf.png");
+     *
+     * @param bmp
+     */
+    public static void saveImageToGallery(Bitmap bmp, String bitName) {
+        File file = new File(bitName);
+        try {
+            FileOutputStream fos = new FileOutputStream(file);
+            bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
+            fos.flush();
+            fos.close();
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /**
+     * 打开本地图片
+     *
+     * @param path
+     * @return
+     */
+    public static Bitmap openImg(String path) {
+
+        Bitmap ret = BitmapFactory.decodeFile(path);
+
+        if (ret == null) {
+            LogUtils.e("打开" + path + "失败!");
+        }
+
+        return ret;
+    }
+
+
+    /**
+     * 单点找色
+     *
+     * @param img
+     * @param color
+     * @return
+     */
+    public static LinkedList<Point> findPoint(Bitmap img, Color color) {
+        LinkedList<Point> pl = new LinkedList<Point>();
+        int width = img.getWidth();
+        int height = img.getHeight();
+        for (int i = 0; i < width; i++) {
+            for (int j = 0; j < height; j++) {
+                if (Color.isSame(getPoint(img, i, j), color)) {
+                    pl.add(new Point(i, j));
+                }
+            }
+        }
+        return pl;
+    }
+
+
+    /**
+     * 多色找点
+     * 在屏幕某个范围内
+     *
+     * @param img
+     * @param colorRules
+     * @param leftX
+     * @param leftY
+     * @param rightX
+     * @param rightY
+     * @return
+     */
+    public static Point findPointByMulColor(Bitmap img, String colorRules, int leftX, int leftY, int rightX, int rightY) {
+        img = cropBitmap(img, leftX, leftY, rightX, rightY);
+        Point p = findPointByMulColor(img, colorRules);
+        if (p.isEmpty()) {
+            return p;
+        }
+        return new Point(p.getX() + leftX, p.getY() + leftY);
+    }
+
+    /**
+     * 多色找点函数
+     *
+     * @param img
+     * @param colorRules
+     * @return
+     */
+    public static Point findPointByMulColor(Bitmap img, String colorRules) {
+        long now = System.currentTimeMillis();
+        int[] colors = new int[img.getWidth() * img.getHeight()];
+        String[] res = colorRules.split(",");
+        Color firstPointColor = HexColor2DecColor(res[0], true);
+        img.getPixels(colors, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight());
+        for (int i = 0; i < colors.length; i++) {
+            if (Color.isSame(new Color(colors[i]), firstPointColor)) {
+                int y = (int) (i / img.getWidth());
+                int x = i % img.getWidth();
+                for (int k = 1; k < res.length; k++) {
+                    res[k] = res[k].replace("\"", "");
+                    String[] info = res[k].split("\\|");
+                    int testX = x + Integer.parseInt(info[0]);
+                    int testY = y + Integer.parseInt(info[1]);
+                    if (testX < 0 || testY < 0 || testX > img.getWidth() || testY > img.getHeight()) {
+                        break;
+                    }
+                    Color nextColor = getPoint(img, testX, testY);
+                    if (!Color.isSame(nextColor, HexColor2DecColor(info[2], true))) {
+                        break;
+                    } else {
+                        if (k == (res.length - 1)) {
+                            LogUtils.i("找点用时:", String.valueOf(System.currentTimeMillis() - now));
+                            return new Point(x, y);
+                        }
+                    }
+                }
+            }
+        }
+        return new Point(-1, -1);
+    }
+
+    /**
+     * 多色找点,自定义颜色误差
+     *
+     * @param img
+     * @param colorRules
+     * @param offset
+     * @return
+     */
+    public static Point findPointByMulColor(Bitmap img, String colorRules, int offset) {
+        long now = System.currentTimeMillis();
+        // 将图像转换成颜色数组
+        int[] colors = new int[img.getWidth() * img.getHeight()];
+        String[] res = colorRules.split(",");
+        Color firstPointColor = HexColor2DecColor(res[0], true);
+        img.getPixels(colors, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight());
+        //遍历颜色数组
+        for (int i = 0; i < colors.length; i++) {
+            // 寻找规则中第一个点
+            if (Color.isSame(new Color(colors[i]), firstPointColor, offset)) {
+                // 第一个点的y坐标
+                int y = (int) (i / img.getWidth());
+                // 第一个点的x坐标
+                int x = i % img.getWidth();
+                // 检查规则中后续每个点
+                for (int k = 1; k < res.length; k++) {
+                    //处理规则中多余的引号
+                    res[k] = res[k].replace("\"", "");
+                    String[] info = res[k].split("\\|");
+                    int testX = x + Integer.parseInt(info[0]);
+                    int testY = y + Integer.parseInt(info[1]);
+                    //超出图片范围
+                    if (testX < 0 || testY < 0 || testX > img.getWidth() || testY > img.getHeight()) {
+                        break;
+                    }
+                    Color nextColor = getPoint(img, testX, testY);
+                    if (!Color.isSame(nextColor, HexColor2DecColor(info[2], true), offset)) {
+                        break;
+                    } else {
+                        if (k == (res.length - 1)) {
+                            return new Point(x, y);
+                        }
+                    }
+                }
+            }
+        }
+        LogUtils.i("找点用时:", String.valueOf(System.currentTimeMillis() - now));
+        return new Point(-1, -1);
+    }
+
+    /**
+     * 已废弃
+     *
+     * @param img
+     * @param colorRules
+     * @return
+     * @deprecated
+     */
+    public static Point findPointByMulColorBack(Bitmap img, String colorRules) {
+        long now = System.currentTimeMillis();
+        String[] res = colorRules.split(",");
+        Color firstPointColor = HexColor2DecColor(res[0], true);
+        int imgWidth = img.getWidth();
+        int imgHeight = img.getHeight();
+        for (int i = 0; i < imgWidth; i++) {
+            for (int j = 0; j < imgHeight; j++) {
+                if (Color.isSame(getPoint(img, i, j), firstPointColor)) {
+                    for (int k = 1; k < res.length; k++) {
+                        res[k] = res[k].replace("\"", "");
+                        String[] info = res[k].split("\\|");
+                        int testX = i + Integer.parseInt(info[0]);
+                        int testY = j + Integer.parseInt(info[1]);
+                        if (testX < 0 || testY < 0 || testX > imgWidth || testY > imgHeight) {
+                            break;
+                        }
+                        Color nextColor = getPoint(img, testX, testY);
+                        if (!Color.isSame(nextColor, HexColor2DecColor(info[2], true))) {
+                            break;
+                        } else {
+                            if (k == (res.length - 1)) {
+                                LogUtils.i("找点用时:", String.valueOf(System.currentTimeMillis() - now));
+                                return new Point(i, j);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        LogUtils.i("找点用时:", String.valueOf(System.currentTimeMillis() - now));
+        return new Point(-1, -1);
+    }
+
+
+    /**
+     * 多色找点,返回屏幕内全部满足规则的点
+     *
+     * @param img
+     * @param colorRules
+     * @return
+     */
+    public static LinkedList<Point> findPointsByMulColor(Bitmap img, String colorRules) {
+        LinkedList<Point> ret = new LinkedList<Point>();
+        String[] res = colorRules.split(",");
+        Color firstPointColor = HexColor2DecColor(res[0], true);
+        int imgWidth = img.getWidth();
+        int imgHeight = img.getHeight();
+
+        for (int i = 0; i < imgWidth; i++) {
+            for (int j = 0; j < imgHeight; j++) {
+                if (Color.isSame(getPoint(img, i, j), firstPointColor)) {
+                    for (int k = 1; k < res.length; k++) {
+                        res[k] = res[k].replace("\"", "");
+                        String[] info = res[k].split("\\|");
+                        int testX = i + Integer.parseInt(info[0]);
+                        int testY = j + Integer.parseInt(info[1]);
+                        if (testX < 0 || testY < 0 || testX > imgWidth || testY > imgHeight) {
+                            break;
+                        }
+                        Color nextColor = getPoint(img, testX, testY);
+                        if (!Color.isSame(nextColor, HexColor2DecColor(info[2], true))) {
+                            break;
+                        } else {
+                            if (k == (res.length - 1)) {
+                                ret.add(new Point(i, j));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return ret;
+    }
+
+
+    /**
+     * @param color
+     * @return
+     */
+    public static Color HexColor2DecColor(String color) {
+        color = color.replace("#", "");
+        color = color.replace("\"", "");
+        try {
+            int r = Integer.parseInt(color.substring(0, 2), 16);
+            int g = Integer.parseInt(color.substring(2, 4), 16);
+            int b = Integer.parseInt(color.substring(4, 6), 16);
+            return new Color(r, g, b);
+
+        } catch (Exception e) {
+            return new Color();
+        }
+    }
+
+    /**
+     * @param color
+     * @param bgr
+     * @return
+     */
+    public static Color HexColor2DecColor(String color, boolean bgr) {
+        color = color.replace("#", "");
+        color = color.replace("\"", "");
+        try {
+            int b = Integer.parseInt(color.substring(0, 2), 16);
+            int g = Integer.parseInt(color.substring(2, 4), 16);
+            int r = Integer.parseInt(color.substring(4, 6), 16);
+            return new Color(r, g, b);
+
+        } catch (Exception e) {
+            return new Color();
+        }
+    }
+
+
+    /**
+     * 获取一个点的颜色
+     *
+     * @param img
+     * @param x
+     * @param y
+     * @return
+     */
+    public static Color getPoint(Bitmap img, int x, int y) {
+        try {
+            return new Color(img.getPixel(x, y));
+        } catch (IllegalArgumentException e) {
+            return new Color(0, 0, 0);
+        }
+
+
+    }
+
+
+    /**
+     * 预览图片
+     *
+     * @param img
+     * @param context
+     */
+//    public static void show(Bitmap img, Context context) {
+//        Dialog dia = new Dialog(context, R.style.edit_AlertDialog_style2);
+//        dia.setContentView(R.layout.activity_start_dialog);
+//        ImageView imageView = (ImageView) dia.findViewById(R.id.start_img);
+//        imageView.setImageBitmap(img);
+//        dia.show();
+//
+//        dia.setCanceledOnTouchOutside(true); // Sets whether this dialog is
+//        Window w = dia.getWindow();
+//        WindowManager.LayoutParams lp = w.getAttributes();
+//        lp.x = 0;
+//        lp.y = 40;
+//        dia.onWindowAttributesChanged(lp);
+//    }
+
+
+    /**
+     * 裁剪
+     *
+     * @param bitmap
+     * @param leftTopX
+     * @param leftTopY
+     * @param rightBottomX
+     * @param rightBottomY
+     * @return
+     */
+    public static Bitmap cropBitmap(Bitmap bitmap, int leftTopX, int leftTopY, int rightBottomX, int rightBottomY) {
+        return Bitmap.createBitmap(bitmap, leftTopX, leftTopY, rightBottomX - leftTopX, rightBottomY - leftTopY, null, false);
+    }
+
+
+    /**
+     * base64 图片
+     *
+     * @param bitmap
+     * @return
+     */
+    public static String encodeImage(Bitmap bitmap) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        //读取图片到ByteArrayOutputStream
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); //参数如果为100那么就不压缩
+        byte[] bytes = baos.toByteArray();
+
+        return Base64.encodeToString(bytes, Base64.DEFAULT);
+
+
+    }
+
+
+    /**
+     * 模板匹配
+     *
+     * @param srcImg      //源图像
+     * @param templateImg //模板图像
+     * @param threshold   //相识度阈值,阈值调小可以一定程度解决不同手机分辨率的问题
+     * @return //如果没有找到则返回(-1,-1)点
+     */
+//    public static Point matchTemplate(Bitmap srcImg, Bitmap templateImg, double threshold) {
+//
+//        if (threshold <= 0) {
+//            threshold = 0.5;
+//        }
+//
+//
+//        Mat tpl = new Mat();
+//        Mat src = new Mat();
+//        Utils.bitmapToMat(srcImg, src);
+//        Utils.bitmapToMat(templateImg, tpl);
+//
+//
+//        int height = src.rows() - tpl.rows() + 1;
+//        int width = src.cols() - tpl.cols() + 1;
+//        Mat result = new Mat(height, width, CvType.CV_32FC1);
+//        int method = Imgproc.TM_CCOEFF_NORMED;
+//        Imgproc.matchTemplate(src, tpl, result, method);
+//        Core.MinMaxLocResult minMaxResult = Core.minMaxLoc(result);
+//        org.opencv.core.Point maxloc = minMaxResult.maxLoc;
+//        if (minMaxResult.maxVal < threshold) {
+//            return new Point(-1, -1);
+//        }
+//        org.opencv.core.Point minloc = minMaxResult.minLoc;
+//        org.opencv.core.Point matchloc = null;
+//        matchloc = maxloc;
+//        return new Point((int) matchloc.x, (int) matchloc.y);
+//
+//    }
+
+
+    /**
+     * 根据给定的宽和高进行resize
+     *
+     * @param origin    原图
+     * @param newWidth  新图的宽
+     * @param newHeight 新图的高
+     * @return new Bitmap
+     */
+    public static Bitmap resize(Bitmap origin, int newWidth, int newHeight) {
+        if (origin == null) {
+            return null;
+        }
+        int height = origin.getHeight();
+        int width = origin.getWidth();
+        float scaleWidth = ((float) newWidth) / width;
+        float scaleHeight = ((float) newHeight) / height;
+        Matrix matrix = new Matrix();
+        matrix.postScale(scaleWidth, scaleHeight);// 使用后乘
+        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
+        if (!origin.isRecycled()) {
+            origin.recycle();
+        }
+        return newBM;
+    }
+
+}

+ 28 - 0
app/src/main/java/org/yameida/worktool/utils/capture/MediaProjectionHolder.kt

@@ -0,0 +1,28 @@
+package org.yameida.worktool.utils.capture
+
+import android.media.projection.MediaProjection
+import android.os.Handler
+import android.os.Looper
+
+/**
+ * Created by Gallon on 2019/8/4.
+ */
+class MediaProjectionHolder {
+
+    companion object {
+        var mMediaProjection: MediaProjection? = null
+
+        @Synchronized
+        fun setMediaProjection(mediaProjection: MediaProjection) {
+            if (mMediaProjection == null) {
+                mMediaProjection = mediaProjection
+                mediaProjection.registerCallback(object : MediaProjection.Callback() {
+                    override fun onStop() {
+                        mMediaProjection = null
+                    }
+                }, Handler(Looper.getMainLooper()))
+            }
+        }
+    }
+
+}

+ 42 - 0
app/src/main/java/org/yameida/worktool/utils/capture/Point.java

@@ -0,0 +1,42 @@
+package org.yameida.worktool.utils.capture;
+
+public class Point {
+
+    private int x = 0;
+    private int y = 0;
+
+    public Point(int x, int y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    public Point() {
+    }
+
+
+    public int getX() {
+        return x;
+    }
+
+    public void setX(int x) {
+        this.x = x;
+    }
+
+    public int getY() {
+        return y;
+    }
+
+    public void setY(int y) {
+        this.y = y;
+    }
+
+    public boolean isEmpty() {
+        return x < 0 || y < 0;
+    }
+
+
+    @Override
+    public String toString() {
+        return "{" + x + "," + y + "}";
+    }
+}

+ 119 - 0
app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtil.java

@@ -0,0 +1,119 @@
+package org.yameida.worktool.utils.capture;
+
+
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.Utils;
+
+
+/**
+ * 截图工具类
+ */
+public class ScreenCaptureUtil {
+
+    private static int screenOrientation;   // 0 未设置  ,1竖屏    2横批
+
+
+    /**
+     * 强制设置为横屏模式
+     */
+    public static void setHorizontalScreen() {
+        ScreenCaptureUtil.screenOrientation = 2;
+    }
+
+
+    /**
+     * 强制设置为竖屏模式
+     */
+    public static void setVerticalScreen() {
+        ScreenCaptureUtil.screenOrientation = 1;
+    }
+
+
+    /**
+     * 获取屏幕图像
+     *
+     * @return
+     */
+    public static Bitmap getScreenCap() {
+        Bitmap o_img;
+        boolean retry = false;
+        do {
+            if (retry) {
+                LogUtils.i("资源已被回收,尝试重试!");
+            }
+            if (ScreenCaptureUtil.screenOrientation == 0) {
+                if (Utils.getApp().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+                    //竖屏
+                    o_img = ScreenCaptureUtilByMediaPro.getScreenCapVertical();
+                } else {
+                    //横屏
+                    o_img = ScreenCaptureUtilByMediaPro.getScreenCapHorizontal();
+                }
+            } else if (ScreenCaptureUtil.screenOrientation == 1) {
+                o_img = ScreenCaptureUtilByMediaPro.getScreenCapVertical();
+            } else {
+                o_img = ScreenCaptureUtilByMediaPro.getScreenCapHorizontal();
+            }
+            retry = true;
+        } while (o_img == null || o_img.isRecycled());
+
+
+        return Bitmap.createBitmap(o_img);
+
+
+//         使用adb方式截图,性能低下,已废弃
+//        if (System.currentTimeMillis() - getScreenTime < 1000) {
+//            return screenCache;
+//        }
+//        byte[] tempBuffer = new byte[100 * 1024 * 1024];
+//        StringBuilder buffer = new StringBuilder(100 * 1024 * 1024);
+//
+//        Process exec = null;
+//        try {
+//            exec = Runtime.getRuntime().exec("su -c /system/bin/screencap -p");
+//
+//
+//            final InputStream inputStream = exec.getInputStream();
+//            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
+//            //清空缓存内容
+//            buffer.setLength(0);
+//            int count;
+//            while ((count = bufferedInputStream.read(tempBuffer)) > 0) {
+//                buffer.append(new String(tempBuffer, 0, count, StandardCharsets.ISO_8859_1));
+//            }
+//            bufferedInputStream.close();
+//            final int retCode = exec.waitFor();
+//            exec.destroy();
+//            tempBuffer = buffer.toString().getBytes(StandardCharsets.ISO_8859_1);
+//            screenCache = BitmapFactory.decodeByteArray(tempBuffer, 0, tempBuffer.length);
+//            getScreenTime = System.currentTimeMillis();
+//            return screenCache;
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        } catch (InterruptedException e) {
+//            e.printStackTrace();
+//        }
+//        throw new NullPointerException("截图失败");
+
+
+    }
+
+    /**
+     * 指定范围截图
+     *
+     * @param leftX
+     * @param leftY
+     * @param rigthX
+     * @param rightY
+     * @return
+     */
+    public static Bitmap getScreenCap(int leftX, int leftY, int rigthX, int rightY) {
+        Bitmap bitmap = getScreenCap();
+        return Image.cropBitmap(bitmap, leftX, leftY, rigthX, rightY);
+    }
+
+
+}

+ 142 - 0
app/src/main/java/org/yameida/worktool/utils/capture/ScreenCaptureUtilByMediaPro.java

@@ -0,0 +1,142 @@
+package org.yameida.worktool.utils.capture;
+
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.media.ImageReader;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+
+import com.blankj.utilcode.util.ScreenUtils;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * 截图的底层实现类
+ * 这个类的方法不要在业务中直接调用
+ * 业务中使用ScreenCaptureUtil类
+ *
+ * @deprecated
+ */
+public class ScreenCaptureUtilByMediaPro {
+    public static MediaProjectionManager mProjectionManager;
+    public static Intent data;
+    public static int resultCode;
+    private static MediaProjection sMediaProjection;
+    private static ImageReader mImageReaderHorizontal;
+    private static ImageReader mImageReaderVertical;
+    private static Handler backgroundHandler;
+
+    private static Bitmap bitmapCacheHorizontal;
+    private static Bitmap bitmapCacheVertical;
+
+    public static void init() {
+        sMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
+        //start capture reader
+        mImageReaderHorizontal = ImageReader.newInstance(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight(),
+                PixelFormat.RGBA_8888, 2);
+        sMediaProjection.createVirtualDisplay(
+                "ScreenShot",
+                ScreenUtils.getScreenWidth(),
+                ScreenUtils.getScreenHeight(),
+                ScreenUtils.getScreenDensityDpi(),
+                DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
+                mImageReaderHorizontal.getSurface(),
+                null,
+                null);
+
+
+        mImageReaderVertical = ImageReader.newInstance(ScreenUtils.getScreenHeight(), ScreenUtils.getScreenWidth(),
+                PixelFormat.RGBA_8888, 2);
+        sMediaProjection.createVirtualDisplay(
+                "ScreenShot",
+                ScreenUtils.getScreenHeight(),
+                ScreenUtils.getScreenWidth(),
+                ScreenUtils.getScreenDensityDpi(),
+                DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
+                mImageReaderVertical.getSurface(),
+                null,
+                null);
+
+
+    }
+
+
+    /**
+     * 勿直接使用!!
+     *
+     * @return
+     */
+    @Deprecated
+    public static Bitmap getScreenCapHorizontal() {
+
+        Bitmap bitmap;
+        android.media.Image image;
+        do {
+            image = mImageReaderHorizontal.acquireLatestImage();
+            if (image == null && bitmapCacheHorizontal != null) {
+                return bitmapCacheHorizontal;
+            }
+        } while (image == null);
+        bitmap = covetBitmap(image);
+        if (bitmapCacheHorizontal != null && !bitmapCacheHorizontal.isRecycled()) {
+            bitmapCacheHorizontal.recycle();
+            bitmapCacheHorizontal = null;
+        }
+        bitmapCacheHorizontal = bitmap;
+        return bitmap;
+    }
+
+
+    /**
+     * 勿直接使用!!
+     *
+     * @return
+     */
+    @Deprecated
+    public static Bitmap getScreenCapVertical() {
+
+        Bitmap bitmap;
+        android.media.Image image;
+        do {
+            image = mImageReaderVertical.acquireLatestImage();
+            if (image == null && bitmapCacheVertical != null) {
+                return bitmapCacheVertical;
+            }
+        } while (image == null);
+        bitmap = covetBitmap(image);
+        if (bitmapCacheVertical != null) {
+            bitmapCacheVertical.recycle();
+            bitmapCacheVertical = null;
+        }
+        bitmapCacheVertical = bitmap;
+        return bitmap;
+    }
+
+
+    public static Bitmap covetBitmap(android.media.Image image) {
+        int width = image.getWidth();
+        int height = image.getHeight();
+        final android.media.Image.Plane[] planes = image.getPlanes();
+        final ByteBuffer buffer = planes[0].getBuffer();
+        //每个像素的间距
+        int pixelStride = planes[0].getPixelStride();
+        //总的间距
+        int rowStride = planes[0].getRowStride();
+        int rowPadding = rowStride - pixelStride * width;
+        Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
+        bitmap.copyPixelsFromBuffer(buffer);
+        bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);
+        image.close();
+        return bitmap;
+    }
+
+
+    public static void stop() {
+        sMediaProjection.stop();
+    }
+}

+ 110 - 0
app/src/main/java/org/yameida/worktool/utils/envcheck/CheckHook.java

@@ -0,0 +1,110 @@
+package org.yameida.worktool.utils.envcheck;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class CheckHook {
+
+    public static boolean isHook(Context context) {
+        return isHookByPackageName(context) || isHookByStack(context) || isHookByJar();
+    }
+
+    /**
+     * 包名检测
+     *
+     * @param context
+     * @return
+     */
+    public static boolean isHookByPackageName(Context context) {
+        boolean isHook = false;
+        PackageManager packageManager = context.getPackageManager();
+        List<ApplicationInfo> applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
+        if (null == applicationInfoList) {
+            return isHook;
+        }
+        for (ApplicationInfo applicationInfo : applicationInfoList) {
+            if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {
+                Log.wtf("HookDetection", "Xposed found on the system.");
+                isHook = true;
+            }
+            if (applicationInfo.packageName.equals("com.saurik.substrate")) {
+                isHook = true;
+                Log.wtf("HookDetection", "Substrate found on the system.");
+            }
+        }
+        return isHook;
+    }
+
+    public static boolean isHookByStack(Context context) {
+        boolean isHook = false;
+        try {
+            throw new Exception("blah");
+        } catch (Exception e) {
+            int zygoteInitCallCount = 0;
+            for (StackTraceElement stackTraceElement : e.getStackTrace()) {
+                if (stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) {
+                    zygoteInitCallCount++;
+                    if (zygoteInitCallCount == 2) {
+                        Log.wtf("HookDetection", "Substrate is active on the device.");
+                        isHook = true;
+                    }
+                }
+                if (stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2") &&
+                        stackTraceElement.getMethodName().equals("invoked")) {
+                    Log.wtf("HookDetection", "A method on the stack trace has been hooked using Substrate.");
+                    isHook = true;
+                }
+                if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") &&
+                        stackTraceElement.getMethodName().equals("main")) {
+                    Log.wtf("HookDetection", "Xposed is active on the device.");
+                    isHook = true;
+                }
+                if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") &&
+                        stackTraceElement.getMethodName().equals("handleHookedMethod")) {
+                    Log.wtf("HookDetection", "A method on the stack trace has been hooked using Xposed.");
+                    isHook = true;
+                }
+
+            }
+        }
+        return isHook;
+    }
+
+    public static boolean isHookByJar() {
+        boolean isHook = false;
+        try {
+            Set<String> libraries = new HashSet();
+            String mapsFilename = "/proc/" + android.os.Process.myPid() + "/maps";
+            BufferedReader reader = new BufferedReader(new FileReader(mapsFilename));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.endsWith(".so") || line.endsWith(".jar")) {
+                    int n = line.lastIndexOf(" ");
+                    libraries.add(line.substring(n + 1));
+                }
+            }
+            for (String library : libraries) {
+                if (library.contains("com.saurik.substrate")) {
+                    Log.wtf("HookDetection", "Substrate shared object found: " + library);
+                    isHook = true;
+                }
+                if (library.contains("XposedBridge.jar")) {
+                    Log.wtf("HookDetection", "Xposed JAR found: " + library);
+                    isHook = true;
+                }
+            }
+            reader.close();
+        } catch (Exception e) {
+            Log.wtf("HookDetection", e.toString());
+        }
+        return isHook;
+    }
+}

+ 225 - 0
app/src/main/java/org/yameida/worktool/utils/envcheck/CheckRoot.java

@@ -0,0 +1,225 @@
+package org.yameida.worktool.utils.envcheck;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+
+public class CheckRoot {
+    private static String LOG_TAG = CheckRoot.class.getName();
+
+    public static boolean isDeviceRooted() {
+        if (checkDeviceDebuggable()) {
+            Log.i(LOG_TAG, "isDeviceRooted: checkDeviceDebuggable()");
+            return true;
+        }
+        if (checkSuperuserApk()) {
+            Log.i(LOG_TAG, "isDeviceRooted: checkSuperuserApk()");
+            return true;
+        }
+        if (checkRootPathSU()) {
+            Log.i(LOG_TAG, "isDeviceRooted: checkRootPathSU()");
+            return true;
+        }
+        if (checkRootWhichSU()) {
+            Log.i(LOG_TAG, "isDeviceRooted: checkRootWhichSU()");
+            return true;
+        }
+
+        if (checkAccessRootData()) {
+            Log.i(LOG_TAG, "isDeviceRooted: checkAccessRootData()");
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean checkDeviceDebuggable() {
+        String buildTags = android.os.Build.TAGS;
+        if (buildTags != null && buildTags.contains("test-keys")) {
+            Log.i(LOG_TAG, "buildTags=" + buildTags);
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean checkSuperuserApk() {
+        try {
+            File file = new File("/system/app/Superuser.apk");
+            if (file.exists()) {
+                Log.i(LOG_TAG, "/system/app/Superuser.apk exist");
+                return true;
+            }
+        } catch (Exception e) {
+        }
+        return false;
+    }
+
+    public static boolean checkRootPathSU() {
+        File f = null;
+        final String kSuSearchPaths[] = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/"};
+        try {
+            for (int i = 0; i < kSuSearchPaths.length; i++) {
+                f = new File(kSuSearchPaths[i] + "su");
+                if (f != null && f.exists()) {
+                    Log.i(LOG_TAG, "find su in : " + kSuSearchPaths[i]);
+                    return true;
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    public static boolean checkRootWhichSU() {
+        String[] strCmd = new String[]{"/system/xbin/which", "su"};
+        ArrayList<String> execResult = executeCommand(strCmd);
+        if (execResult != null) {
+            Log.i(LOG_TAG, "execResult=" + execResult.toString());
+            return true;
+        } else {
+            Log.i(LOG_TAG, "execResult=null");
+            return false;
+        }
+    }
+
+    public static ArrayList<String> executeCommand(String[] shellCmd) {
+        String line = null;
+        ArrayList<String> fullResponse = new ArrayList<String>();
+        Process localProcess = null;
+        try {
+            Log.i(LOG_TAG, "to shell exec which for find su :");
+            localProcess = Runtime.getRuntime().exec(shellCmd);
+        } catch (Exception e) {
+            return null;
+        }
+        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream()));
+        BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream()));
+        try {
+            while ((line = in.readLine()) != null) {
+                Log.i(LOG_TAG, "–> Line received: " + line);
+                fullResponse.add(line);
+            }
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
+        }
+        Log.i(LOG_TAG, "–> Full response was: " + fullResponse);
+        return fullResponse;
+    }
+
+    public static synchronized boolean checkGetRootAuth() {
+        Process process = null;
+        DataOutputStream os = null;
+        try {
+            Log.i(LOG_TAG, "to exec su");
+            process = Runtime.getRuntime().exec("su");
+            os = new DataOutputStream(process.getOutputStream());
+            os.writeBytes("exit\n");
+            os.flush();
+            int exitValue = process.waitFor();
+            Log.i(LOG_TAG, "exitValue=" + exitValue);
+            if (exitValue == 0) {
+                return true;
+            } else {
+                return false;
+            }
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
+                    + e.getMessage());
+            return false;
+        } finally {
+            try {
+                if (os != null) {
+                    os.close();
+                }
+                process.destroy();
+            } catch (Exception e) {
+                Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
+            }
+        }
+    }
+
+    public static synchronized boolean checkBusybox() {
+        try {
+            Log.i(LOG_TAG, "to exec busybox df");
+            String[] strCmd = new String[]{"busybox", "df"};
+            ArrayList<String> execResult = executeCommand(strCmd);
+            if (execResult != null) {
+                Log.i(LOG_TAG, "execResult=" + execResult.toString());
+                return true;
+            } else {
+                Log.i(LOG_TAG, "execResult=null");
+                return false;
+            }
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
+                    + e.getMessage());
+            return false;
+        }
+    }
+
+    public static synchronized boolean checkAccessRootData() {
+        try {
+            Log.i(LOG_TAG, "to write /data");
+            String fileContent = "test_ok";
+            Boolean writeFlag = writeFile("/data/su_test", fileContent);
+            if (writeFlag) {
+                Log.i(LOG_TAG, "write ok");
+            } else {
+                Log.i(LOG_TAG, "write failed");
+            }
+
+            Log.i(LOG_TAG, "to read /data");
+            String strRead = readFile("/data/su_test");
+            Log.i(LOG_TAG, "strRead=" + strRead);
+            if (fileContent.equals(strRead)) {
+                return true;
+            } else {
+                return false;
+            }
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
+            return false;
+        }
+    }
+
+    public static Boolean writeFile(String fileName, String message) {
+        try {
+            FileOutputStream fout = new FileOutputStream(fileName);
+            byte[] bytes = message.getBytes();
+            fout.write(bytes);
+            fout.close();
+            return true;
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
+            return false;
+        }
+    }
+
+    public static String readFile(String fileName) {
+        File file = new File(fileName);
+        try {
+            FileInputStream fis = new FileInputStream(file);
+            byte[] bytes = new byte[1024];
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            int len;
+            while ((len = fis.read(bytes)) > 0) {
+                bos.write(bytes, 0, len);
+            }
+            String result = new String(bos.toByteArray());
+            Log.i(LOG_TAG, result);
+            return result;
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
+            return null;
+        }
+    }
+}

二進制
app/src/main/res/drawable-xxhdpi/good_morning_img.png


二進制
app/src/main/res/drawable-xxhdpi/good_night_img.png


+ 11 - 0
app/src/main/res/drawable/buttonshapewhitebg.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="8dp" />
+            <solid android:color="@android:color/transparent"/>
+            <stroke android:color="#96ffffff"
+                android:width="2dp"/>
+        </shape>
+    </item>
+</selector>

+ 22 - 0
app/src/main/res/drawable/comment_red_btn.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape>
+            <solid android:color="@color/color_dashen_passed" />
+            <corners android:radius="5dp" />
+        </shape>
+    </item>
+    <item android:state_enabled="false">
+        <shape>
+            <solid android:color="@android:color/transparent" />
+            <corners android:radius="5dp" />
+            <stroke android:width="1dp" android:color="@color/c8c8c8" />
+        </shape>
+    </item>
+    <item>
+        <shape>
+            <solid android:color="@color/color_dashen" />
+            <corners android:radius="5dp" />
+        </shape>
+    </item>
+</selector>

+ 9 - 0
app/src/main/res/drawable/ic_email_white_24dp.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#96ffffff"
+        android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_lock_white_24dp.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#96ffffff"
+        android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
+</vector>

二進制
app/src/main/res/font/calibri.ttf


+ 232 - 0
app/src/main/res/layout/activity_accessibility_guide.xml

@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        android:layout_marginStart="32dp"
+        android:layout_marginEnd="32dp"
+        android:text="允许开启无障碍服务"
+        android:textColor="@color/color_333333"
+        android:textSize="22sp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="18dp"
+        android:layout_marginStart="18dp"
+        android:layout_marginEnd="18dp"
+        android:text="请开启无障碍服务,以使用WorkTool主功能"
+        android:textColor="@color/color_333333"
+        android:textSize="14sp" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="12dp"
+        android:background="@drawable/bg_float_guide">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <RelativeLayout
+                android:id="@+id/rl_guide_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <ImageView
+                    android:id="@+id/iv_guide_icon"
+                    android:layout_width="75dp"
+                    android:layout_height="75dp"
+                    android:layout_marginBottom="15dp"
+                    android:layout_marginStart="20dp"
+                    android:layout_marginTop="18dp"
+                    android:src="@mipmap/ic_launcher" />
+
+                <TextView
+                    android:id="@+id/tv_guide_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="18dp"
+                    android:layout_toEndOf="@id/iv_guide_icon"
+                    android:text="@string/app_name"
+                    android:textColor="#000"
+                    android:textSize="22sp" />
+
+            </RelativeLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_guide_permit"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/rl_guide_title"
+                android:layout_marginTop="10dp"
+                android:layout_marginStart="30dp"
+                android:layout_marginEnd="30dp"
+                android:orientation="vertical">
+
+                <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:background="@color/bar_gray">
+
+                    <ImageView
+                        android:layout_width="30dp"
+                        android:layout_height="30dp"
+                        android:layout_margin="10dp"
+                        android:scaleX="0.9"
+                        android:scaleY="0.9"
+                        android:src="@drawable/ic_arrow_back"
+                        />
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="65dp"
+                        android:layout_gravity="center_vertical"
+                        android:textSize="21sp"
+                        android:textColor="@color/white"
+                        android:text="无障碍"
+                        />
+
+                    <ImageView
+                        android:layout_width="30dp"
+                        android:layout_height="30dp"
+                        android:layout_margin="10dp"
+                        android:layout_gravity="end"
+                        android:src="@drawable/abc_ic_menu_moreoverflow_mtrl_alpha"
+                        />
+
+                </FrameLayout>
+
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="0.5dp"
+                    android:background="@color/color_b2000000"
+                    />
+
+                <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:background="@color/bar_gray">
+
+                    <ImageView
+                        android:layout_width="30dp"
+                        android:layout_height="30dp"
+                        android:layout_margin="10dp"
+                        android:src="@mipmap/ic_launcher"
+                        />
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="65dp"
+                        android:layout_gravity="center_vertical"
+                        android:textSize="21sp"
+                        android:textColor="@color/white"
+                        android:text="@string/app_name"
+                        />
+
+                </FrameLayout>
+
+                <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="50dp"
+                    android:background="@color/while_bg">
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="5dp"
+                        android:layout_gravity="center_vertical"
+                        android:textSize="17sp"
+                        android:textColor="@color/qmui_config_color_black"
+                        android:text="使用WorkTool"
+                        />
+
+                    <ImageView
+                        android:layout_width="50dp"
+                        android:layout_height="50dp"
+                        android:layout_gravity="end"
+                        android:src="@drawable/widget_tip_button"
+                        />
+
+                </FrameLayout>
+
+            </LinearLayout>
+
+            <ImageView
+                android:id="@+id/iv_over_finger"
+                android:layout_width="55dp"
+                android:layout_height="55dp"
+                android:layout_alignParentEnd="true"
+                android:layout_below="@id/ll_guide_permit"
+                android:layout_marginEnd="23dp"
+                android:layout_marginTop="-15dp"
+                android:src="@drawable/widget_guide_finger"
+                />
+
+        </RelativeLayout>
+
+    </RelativeLayout>
+
+    <TextView
+        android:id="@+id/tv_float_allow"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="30dp"
+        android:layout_marginStart="30dp"
+        android:layout_marginTop="30dp"
+        android:background="@drawable/comment_red_btn"
+        android:gravity="center"
+        android:paddingBottom="7dp"
+        android:paddingTop="7dp"
+        android:text="允许"
+        android:textColor="@color/white"
+        android:textSize="16sp" />
+
+    <TextView
+        android:id="@+id/tv_float_reject"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="30dp"
+        android:layout_marginStart="30dp"
+        android:layout_marginTop="15dp"
+        android:background="@drawable/comment_gray_btn"
+        android:gravity="center"
+        android:paddingBottom="7dp"
+        android:paddingTop="7dp"
+        android:text="暂不使用"
+        android:textColor="@color/white"
+        android:textSize="16sp" />
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="15dp">
+
+        <CheckBox
+            android:id="@+id/cb_guide_not"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="2dp" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_toEndOf="@id/cb_guide_not"
+            android:text="不再询问"
+            android:textColor="@color/color_333333"
+            android:textSize="14sp" />
+
+    </RelativeLayout>
+
+</LinearLayout>

+ 16 - 0
app/src/main/res/layout/activity_browser.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusableInTouchMode="true">
+
+    <com.qmuiteam.qmui.widget.webview.QMUIWebView
+        android:id="@+id/qmwv"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        >
+
+    </com.qmuiteam.qmui.widget.webview.QMUIWebView>
+
+
+</RelativeLayout>

+ 232 - 0
app/src/main/res/layout/activity_float_guide.xml

@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        android:layout_marginStart="32dp"
+        android:layout_marginEnd="32dp"
+        android:text="允许弹出悬浮窗口"
+        android:textColor="@color/color_333333"
+        android:textSize="22sp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="18dp"
+        android:layout_marginStart="18dp"
+        android:layout_marginEnd="18dp"
+        android:text="请允许弹窗权限,以使用悬浮窗按钮和其他完整功能"
+        android:textColor="@color/color_333333"
+        android:textSize="14sp" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="12dp"
+        android:background="@drawable/bg_float_guide">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <RelativeLayout
+                android:id="@+id/rl_guide_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <ImageView
+                    android:id="@+id/iv_guide_icon"
+                    android:layout_width="75dp"
+                    android:layout_height="75dp"
+                    android:layout_marginBottom="15dp"
+                    android:layout_marginStart="20dp"
+                    android:layout_marginTop="18dp"
+                    android:src="@mipmap/ic_launcher" />
+
+                <TextView
+                    android:id="@+id/tv_guide_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="18dp"
+                    android:layout_toEndOf="@id/iv_guide_icon"
+                    android:text="@string/app_name"
+                    android:textColor="#000"
+                    android:textSize="22sp" />
+
+            </RelativeLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_guide_permit"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/rl_guide_title"
+                android:layout_marginTop="10dp"
+                android:layout_marginStart="30dp"
+                android:layout_marginEnd="30dp"
+                android:orientation="vertical">
+
+                <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:background="@color/bar_gray">
+
+                    <ImageView
+                        android:layout_width="30dp"
+                        android:layout_height="30dp"
+                        android:layout_margin="10dp"
+                        android:scaleX="0.9"
+                        android:scaleY="0.9"
+                        android:src="@drawable/ic_arrow_back"
+                        />
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="65dp"
+                        android:layout_gravity="center_vertical"
+                        android:textSize="21sp"
+                        android:textColor="@color/white"
+                        android:text="应用权限"
+                        />
+
+                    <ImageView
+                        android:layout_width="30dp"
+                        android:layout_height="30dp"
+                        android:layout_margin="10dp"
+                        android:layout_gravity="end"
+                        android:src="@drawable/abc_ic_menu_moreoverflow_mtrl_alpha"
+                        />
+
+                </FrameLayout>
+
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="0.5dp"
+                    android:background="@color/color_b2000000"
+                    />
+
+                <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:background="@color/bar_gray">
+
+                    <ImageView
+                        android:layout_width="30dp"
+                        android:layout_height="30dp"
+                        android:layout_margin="10dp"
+                        android:src="@mipmap/ic_launcher"
+                        />
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="65dp"
+                        android:layout_gravity="center_vertical"
+                        android:textSize="21sp"
+                        android:textColor="@color/white"
+                        android:text="@string/app_name"
+                        />
+
+                </FrameLayout>
+
+                <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="50dp"
+                    android:background="@color/while_bg">
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="5dp"
+                        android:layout_gravity="center_vertical"
+                        android:textSize="17sp"
+                        android:textColor="@color/qmui_config_color_black"
+                        android:text="允许覆盖其他应用程序"
+                        />
+
+                    <ImageView
+                        android:layout_width="50dp"
+                        android:layout_height="50dp"
+                        android:layout_gravity="end"
+                        android:src="@drawable/widget_tip_button"
+                        />
+
+                </FrameLayout>
+
+            </LinearLayout>
+
+            <ImageView
+                android:id="@+id/iv_over_finger"
+                android:layout_width="55dp"
+                android:layout_height="55dp"
+                android:layout_alignParentEnd="true"
+                android:layout_below="@id/ll_guide_permit"
+                android:layout_marginEnd="23dp"
+                android:layout_marginTop="-15dp"
+                android:src="@drawable/widget_guide_finger"
+                />
+
+        </RelativeLayout>
+
+    </RelativeLayout>
+
+    <TextView
+        android:id="@+id/tv_float_allow"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="30dp"
+        android:layout_marginStart="30dp"
+        android:layout_marginTop="30dp"
+        android:background="@drawable/comment_red_btn"
+        android:gravity="center"
+        android:paddingBottom="7dp"
+        android:paddingTop="7dp"
+        android:text="允许"
+        android:textColor="@color/white"
+        android:textSize="16sp" />
+
+    <TextView
+        android:id="@+id/tv_float_reject"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="30dp"
+        android:layout_marginStart="30dp"
+        android:layout_marginTop="15dp"
+        android:background="@drawable/comment_gray_btn"
+        android:gravity="center"
+        android:paddingBottom="7dp"
+        android:paddingTop="7dp"
+        android:text="暂不使用"
+        android:textColor="@color/white"
+        android:textSize="16sp" />
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="15dp">
+
+        <CheckBox
+            android:id="@+id/cb_guide_not"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="2dp" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_toEndOf="@id/cb_guide_not"
+            android:text="不再询问"
+            android:textColor="@color/color_333333"
+            android:textSize="14sp" />
+
+    </RelativeLayout>
+
+</LinearLayout>

+ 365 - 0
app/src/main/res/layout/activity_listen.xml

@@ -0,0 +1,365 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background">
+
+    <RelativeLayout
+        android:id="@+id/rl_bar"
+        android:layout_width="match_parent"
+        android:layout_height="50dp">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:textSize="22sp"
+            android:textStyle="bold"
+            android:textColor="@color/color_dashen"
+            android:text="@string/app_name"
+            />
+
+        <ImageView
+            android:id="@+id/iv_settings"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="10dp"
+            android:layout_marginTop="10dp"
+            android:layout_marginEnd="10dp"
+            android:layout_marginBottom="10dp"
+            android:src="@drawable/tab_settings_check" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/list_divider_line"
+            android:layout_alignParentBottom="true"
+            />
+
+    </RelativeLayout>
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/rl_bar">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/background"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:text="使用流程"
+                android:textColor="@color/float_time_color"
+                android:textSize="@dimen/setting_end_font_size"
+                android:textStyle="bold" />
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_marginEnd="@dimen/setting_end_padding"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="1.填写并点击保存链接号"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/setting_vertical_padding"
+                        android:text="2.开启主功能在无障碍栏目里找到WorkTool并开启"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/setting_vertical_padding"
+                        android:text="3.让本程序放在后台运行,点击进入企业微信保持手机不动屏幕常亮即可"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:text="环境信息"
+                android:textColor="@color/float_time_color"
+                android:textSize="@dimen/setting_end_font_size"
+                android:textStyle="bold" />
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_marginEnd="@dimen/setting_end_padding"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="当前运行host"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:id="@+id/tv_host"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="127.0.0.1"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_marginEnd="@dimen/setting_end_padding"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="当前运行版本"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:id="@+id/tv_version"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="1.0"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_marginEnd="@dimen/setting_end_padding"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="企业微信版本"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:id="@+id/tv_work_version"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="4.0.2"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:text="控制台"
+                android:textColor="@color/float_time_color"
+                android:textSize="@dimen/setting_end_font_size"
+                android:textStyle="bold" />
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <Switch
+                    android:id="@+id/sw_accessibility"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/sw_accessibility"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="开启主功能"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <Switch
+                    android:id="@+id/sw_overlay"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/sw_overlay"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="开启悬浮窗权限"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_marginEnd="@dimen/setting_end_padding"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="链接号"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <EditText
+                        android:id="@+id/et_channel"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:hint="请输入申请的机器人ID"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginTop="15dp"
+                android:orientation="vertical">
+
+                <Button
+                    android:id="@+id/bt_save"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:background="@drawable/comment_red_btn"
+                    android:paddingStart="50dp"
+                    android:paddingEnd="50dp"
+                    android:textSize="18sp"
+                    android:textStyle="bold"
+                    android:textColor="@color/white"
+                    android:text="保存" />
+
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</RelativeLayout>

+ 181 - 0
app/src/main/res/layout/activity_login.xml

@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusableInTouchMode="true">
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <ImageView
+            android:id="@+id/imageView"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop"
+            android:src="@drawable/good_morning_img" />
+
+    </FrameLayout>
+
+
+    <LinearLayout
+        android:id="@+id/linearLayout"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="80dp"
+        android:orientation="horizontal">
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Good "
+                android:textColor="#ffffff"
+                android:textSize="32sp" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/textView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Morning"
+                android:textColor="#ffffff"
+                android:textSize="32sp" />
+
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="2dp"
+                android:layout_marginTop="2dp"
+                android:background="#deff00" />
+
+        </LinearLayout>
+
+
+    </LinearLayout>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/linearLayout"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="4dp"
+        android:gravity="center"
+        android:text="WorkTool 极致的自动化体验"
+        android:textColor="#9affffff"
+        android:textSize="10sp"
+        tools:ignore="SmallSp" />
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="32dp"
+        android:orientation="vertical"
+        android:paddingLeft="32dp"
+        android:paddingRight="32dp">
+
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColorHint="#96ffffff"
+            android:theme="@style/EditScreenTextInputLayoutStyle">
+
+            <EditText
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:drawablePadding="16dp"
+                android:drawableEnd="@drawable/ic_email_white_24dp"
+                android:fontFamily="@font/calibri"
+                android:hint="邮件/用户名"
+                android:inputType="textEmailAddress"
+                android:maxLines="1"
+                android:textColor="@android:color/white"
+                android:textSize="16sp" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColorHint="#96ffffff"
+            android:theme="@style/EditScreenTextInputLayoutStyle">
+
+            <EditText
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:drawablePadding="16dp"
+                android:drawableEnd="@drawable/ic_lock_white_24dp"
+                android:fontFamily="@font/calibri"
+                android:hint="密码"
+                android:inputType="textPassword"
+                android:maxLines="1"
+                android:textColor="@android:color/white"
+                android:textSize="16sp" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="8dp"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="16dp"
+                android:background="@drawable/buttonshapewhitebg"
+                android:fontFamily="@font/calibri"
+                android:text="注册"
+                android:textAllCaps="false"
+                android:textStyle="bold"
+                android:textColor="#96ffffff"
+                android:textSize="16sp" />
+
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="16dp"
+                android:background="@drawable/buttonshapewhitebg"
+                android:fontFamily="@font/calibri"
+                android:text="登录"
+                android:textAllCaps="false"
+                android:textColor="#96ffffff"
+                android:textSize="16sp"
+                android:textStyle="bold" />
+
+
+        </LinearLayout>
+
+
+        <TextView
+            android:id="@+id/tv_visitor_login"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:fontFamily="@font/calibri"
+            android:text="游客登录"
+            android:textAlignment="center"
+            android:textColor="#96ffffff"
+            android:textSize="16sp" />
+
+
+    </LinearLayout>
+
+
+</RelativeLayout>

+ 666 - 0
app/src/main/res/layout/activity_settings.xml

@@ -0,0 +1,666 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background">
+
+    <RelativeLayout
+        android:id="@+id/rl_bar"
+        android:layout_width="match_parent"
+        android:layout_height="50dp">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:textSize="22sp"
+            android:textStyle="bold"
+            android:textColor="@color/color_dashen"
+            android:text="@string/app_name"
+            />
+
+        <ImageView
+            android:id="@+id/iv_back_left"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="10dp"
+            android:layout_marginTop="10dp"
+            android:layout_marginEnd="10dp"
+            android:layout_marginBottom="10dp"
+            android:src="@drawable/back_icon" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/list_divider_line"
+            android:layout_alignParentBottom="true"
+            />
+
+    </RelativeLayout>
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/rl_bar">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/background"
+            android:orientation="vertical">
+
+            <!-- 配置 -->
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:text="配置"
+                android:textColor="@color/float_time_color"
+                android:textSize="@dimen/setting_end_font_size"
+                android:textStyle="bold" />
+
+            <RelativeLayout
+                android:id="@+id/rl_reply_strategy"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_reply_strategy_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_hq" />
+
+                <TextView
+                    android:id="@+id/tv_select_reply_strategy"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_reply_strategy"
+                    android:layout_toEndOf="@id/iv_rec_reply_strategy_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="回复策略"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="设置新消息回复的策略"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_rec_orientation"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_orientation_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_orientation" />
+
+                <TextView
+                    android:id="@+id/tv_select_orientation"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_orientation"
+                    android:layout_toEndOf="@id/iv_rec_orientation_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="方向"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text=""
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_rec_location"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_location_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_directory" />
+
+                <TextView
+                    android:id="@+id/tv_select_location"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_location"
+                    android:layout_toEndOf="@id/iv_rec_location_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="save_location"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:id="@+id/tv_save_location_tip"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="video_default_path"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <!-- 控制台 -->
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:text="控制台"
+                android:textColor="@color/float_time_color"
+                android:textSize="@dimen/setting_end_font_size"
+                android:textStyle="bold" />
+
+            <RelativeLayout
+                android:id="@+id/rl_encrypt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_encrypt"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_no_pop" />
+
+                <Switch
+                    android:id="@+id/sw_encrypt"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/sw_encrypt"
+                    android:layout_toEndOf="@id/iv_encrypt"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="开启通讯加密"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="阻止网络抓包截取数据"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_rec_resolution"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_resolution_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_hd" />
+
+                <Switch
+                    android:id="@+id/sw_receive"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <TextView
+                    android:id="@+id/tv_select_resolution"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_resolution"
+                    android:layout_toEndOf="@id/iv_rec_resolution_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="开启新消息接收"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="查看会话列表的所有未读消息"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_advance"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_advance_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_language" />
+
+                <TextView
+                    android:id="@+id/tv_select_advance"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_advance"
+                    android:layout_toEndOf="@id/iv_rec_advance_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="高级选项"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="点击查看更多高级选项"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <!-- 其他 -->
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:text="其他"
+                android:textColor="@color/float_time_color"
+                android:textSize="@dimen/setting_end_font_size"
+                android:textStyle="bold" />
+
+            <RelativeLayout
+                android:id="@+id/rl_log"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_log_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_fps" />
+
+                <TextView
+                    android:id="@+id/tv_select_log"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_log"
+                    android:layout_toEndOf="@id/iv_rec_log_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="客户端日志"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="提供客户端日志给开发者"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_update"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_update_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:scaleX="1.1"
+                    android:scaleY="1.1"
+                    android:src="@drawable/settings_directory" />
+
+                <TextView
+                    android:id="@+id/tv_select_update"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_update"
+                    android:layout_toEndOf="@id/iv_rec_update_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="检查新版本"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="版本更新检查"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_donate"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_donate_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_rate_us" />
+
+                <TextView
+                    android:id="@+id/tv_select_donate"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_donate"
+                    android:layout_toEndOf="@id/iv_rec_donate_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="赞助我们"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="还有机会成为我们的内测用户体验新功能"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_share"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <ImageView
+                    android:id="@+id/iv_rec_share_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:scaleX="1.1"
+                    android:scaleY="1.1"
+                    android:src="@drawable/settings_share" />
+
+                <TextView
+                    android:id="@+id/tv_select_share"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_share"
+                    android:layout_toEndOf="@id/iv_rec_share_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="分享应用"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="把本应用分享给其他人"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+
+            </RelativeLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginTop="20dp"
+                android:orientation="vertical">
+
+                <Button
+                    android:id="@+id/bt_open_main"
+                    style="@style/rec_top_btn"
+                    android:background="@drawable/comment_red_btn"
+                    android:text="开启主功能"
+                    />
+
+                <Button
+                    android:id="@+id/bt_open_flow"
+                    style="@style/rec_top_btn"
+                    android:background="@drawable/comment_red_btn"
+                    android:text="开启悬浮窗" />
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</RelativeLayout>

+ 740 - 0
app/src/main/res/layout/activity_settings_advance.xml

@@ -0,0 +1,740 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background">
+
+    <RelativeLayout
+        android:id="@+id/rl_bar"
+        android:layout_width="match_parent"
+        android:layout_height="50dp">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:text="高级选项"
+            android:textColor="@color/color_dashen"
+            android:textSize="22sp"
+            android:textStyle="bold" />
+
+        <ImageView
+            android:id="@+id/iv_back_left"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="10dp"
+            android:layout_marginTop="10dp"
+            android:layout_marginEnd="10dp"
+            android:layout_marginBottom="10dp"
+            android:src="@drawable/back_icon" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:layout_alignParentBottom="true"
+            android:background="@color/list_divider_line" />
+
+    </RelativeLayout>
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/rl_bar">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/background"
+            android:orientation="vertical">
+
+            <!-- 配置 -->
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_start_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:text="配置"
+                android:textColor="@color/float_time_color"
+                android:textSize="@dimen/setting_end_font_size"
+                android:textStyle="bold" />
+
+            <RelativeLayout
+                android:id="@+id/rl_full_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <Switch
+                    android:id="@+id/sw_full_name"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/sw_full_name"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="获取群聊全称"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="群聊过长时获取群聊全称(性能降低)"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_qr_code"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <Switch
+                    android:id="@+id/sw_qr_code"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/sw_qr_code"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="获取群二维码"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="更新群信息时回调二维码(性能降低)"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_auto_publish"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <Switch
+                    android:id="@+id/sw_auto_publish"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/sw_auto_publish"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="自动发布朋友圈"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="收到朋友圈发布任务时自动发送"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_old_device"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <Switch
+                    android:id="@+id/sw_old_device"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/sw_old_device"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="防卡顿模式"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="开启此功能以提高稳定性(性能降低)"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_auto_pass_friend_request"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <Switch
+                    android:id="@+id/sw_auto_pass_friend_request"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/sw_auto_pass_friend_request"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="自动通过好友请求"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="开启此功能自动通过好友请求"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_qa_url"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding">
+
+                <TextView
+                    android:id="@+id/tv_select_qa_url"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_qa_url"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="设置消息回调"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="回调地址接收处理新消息(详见文档)"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_rec_orientation"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:visibility="gone">
+
+                <ImageView
+                    android:id="@+id/iv_rec_orientation_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_orientation" />
+
+                <TextView
+                    android:id="@+id/tv_select_orientation"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_orientation"
+                    android:layout_toEndOf="@id/iv_rec_orientation_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="方向"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text=""
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:id="@+id/rl_rec_location"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/setting_start_padding"
+                android:paddingTop="@dimen/setting_vertical_padding"
+                android:paddingEnd="@dimen/setting_end_padding"
+                android:paddingBottom="@dimen/setting_vertical_padding"
+                android:visibility="gone">
+
+                <ImageView
+                    android:id="@+id/iv_rec_location_"
+                    android:layout_width="@dimen/setting_start_image_width"
+                    android:layout_height="@dimen/setting_start_image_width"
+                    android:layout_centerVertical="true"
+                    android:src="@drawable/settings_directory" />
+
+                <TextView
+                    android:id="@+id/tv_select_location"
+                    android:layout_width="@dimen/setting_end_font_width"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_end_start_padding"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/setting_start_padding"
+                    android:layout_toStartOf="@id/tv_select_location"
+                    android:layout_toEndOf="@id/iv_rec_location_"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="save_location"
+                        android:textColor="@color/color_333333"
+                        android:textSize="@dimen/setting_start_font_size" />
+
+                    <TextView
+                        android:id="@+id/tv_save_location_tip"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="video_default_path"
+                        android:textColor="@color/color_999999"
+                        android:textSize="@dimen/setting_end_font_size" />
+
+                </LinearLayout>
+            </RelativeLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_corp_param"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@color/background"
+                android:orientation="vertical">
+
+                <!-- 企微后台 -->
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_start_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding"
+                    android:text="企微后台参数"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold" />
+
+                <RelativeLayout
+                    android:id="@+id/rl_corp_name"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding">
+
+                    <TextView
+                        android:id="@+id/tv_select_corp_name"
+                        android:layout_width="@dimen/setting_end_font_width"
+                        android:layout_height="wrap_content"
+                        android:layout_alignParentEnd="true"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_end_start_padding"
+                        android:textColor="@color/float_time_color"
+                        android:textSize="@dimen/setting_end_font_size"
+                        android:textStyle="bold" />
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_start_padding"
+                        android:layout_toStartOf="@id/tv_select_corp_name"
+                        android:orientation="vertical">
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="企业名称(单企业可不填)"
+                            android:textColor="@color/color_333333"
+                            android:textSize="@dimen/setting_start_font_size" />
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="在企业切换时自动切换参数"
+                            android:textColor="@color/color_999999"
+                            android:textSize="@dimen/setting_end_font_size" />
+
+                    </LinearLayout>
+                </RelativeLayout>
+
+                <RelativeLayout
+                    android:id="@+id/rl_corp"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_start_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_start_padding"
+                        android:orientation="vertical">
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="CorpId"
+                            android:textColor="@color/color_333333"
+                            android:textSize="@dimen/setting_start_font_size" />
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="企微后台的企业id"
+                            android:textColor="@color/color_999999"
+                            android:textSize="@dimen/setting_end_font_size" />
+
+                    </LinearLayout>
+
+                </RelativeLayout>
+
+                <RelativeLayout
+                    android:id="@+id/rl_agent"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_start_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_start_padding"
+                        android:orientation="vertical">
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="AgentId"
+                            android:textColor="@color/color_333333"
+                            android:textSize="@dimen/setting_start_font_size" />
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="企微后台应用的AgentId"
+                            android:textColor="@color/color_999999"
+                            android:textSize="@dimen/setting_end_font_size" />
+
+                    </LinearLayout>
+
+                </RelativeLayout>
+
+                <RelativeLayout
+                    android:id="@+id/rl_schema"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_start_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_start_padding"
+                        android:orientation="vertical">
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="Schema"
+                            android:textColor="@color/color_333333"
+                            android:textSize="@dimen/setting_start_font_size" />
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="企微后台应用的Schema"
+                            android:textColor="@color/color_999999"
+                            android:textSize="@dimen/setting_end_font_size" />
+
+                    </LinearLayout>
+
+                </RelativeLayout>
+
+                <RelativeLayout
+                    android:id="@+id/rl_username"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_start_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_start_padding"
+                        android:orientation="vertical">
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="UserName"
+                            android:textColor="@color/color_333333"
+                            android:textSize="@dimen/setting_start_font_size" />
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="应用绑的小程序原始Id(gh_xxx)"
+                            android:textColor="@color/color_999999"
+                            android:textSize="@dimen/setting_end_font_size" />
+
+                    </LinearLayout>
+
+                </RelativeLayout>
+
+                <RelativeLayout
+                    android:id="@+id/rl_signature"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_start_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_start_padding"
+                        android:orientation="vertical">
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="Signature"
+                            android:textColor="@color/color_333333"
+                            android:textSize="@dimen/setting_start_font_size" />
+
+                        <TextView
+                            android:id="@+id/tv_signature"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="企微后台使用的APP签名"
+                            android:textColor="@color/color_999999"
+                            android:textSize="@dimen/setting_end_font_size" />
+
+                    </LinearLayout>
+
+                </RelativeLayout>
+
+                <!-- 其他 -->
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_start_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding"
+                    android:text="其他"
+                    android:textColor="@color/float_time_color"
+                    android:textSize="@dimen/setting_end_font_size"
+                    android:textStyle="bold"
+                    android:visibility="gone" />
+
+                <RelativeLayout
+                    android:id="@+id/rl_language"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/setting_start_padding"
+                    android:paddingTop="@dimen/setting_vertical_padding"
+                    android:paddingEnd="@dimen/setting_end_padding"
+                    android:paddingBottom="@dimen/setting_vertical_padding"
+                    android:visibility="gone">
+
+                    <ImageView
+                        android:id="@+id/iv_rec_language_"
+                        android:layout_width="@dimen/setting_start_image_width"
+                        android:layout_height="@dimen/setting_start_image_width"
+                        android:layout_centerVertical="true"
+                        android:src="@drawable/settings_language" />
+
+                    <TextView
+                        android:id="@+id/tv_select_language"
+                        android:layout_width="@dimen/setting_end_font_width"
+                        android:layout_height="wrap_content"
+                        android:layout_alignParentEnd="true"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_end_start_padding"
+                        android:textColor="@color/float_time_color"
+                        android:textSize="@dimen/setting_end_font_size"
+                        android:textStyle="bold" />
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_centerVertical="true"
+                        android:layout_marginStart="@dimen/setting_start_padding"
+                        android:layout_toStartOf="@id/tv_select_language"
+                        android:layout_toEndOf="@id/iv_rec_language_"
+                        android:orientation="vertical">
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="language"
+                            android:textColor="@color/color_333333"
+                            android:textSize="@dimen/setting_start_font_size" />
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:text="language_tips"
+                            android:textColor="@color/color_999999"
+                            android:textSize="@dimen/setting_end_font_size"
+                            android:visibility="gone" />
+
+                    </LinearLayout>
+
+                </RelativeLayout>
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</RelativeLayout>

二進制
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


二進制
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


+ 50 - 0
app/src/main/res/values/colors-rec.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="background">@color/color_f5f5f5</color>
+    <color name="list_divider_line">@color/color_e5e5e5</color>
+    <color name="white">#fff</color>
+    <color name="color_323232">#323232</color>
+    <color name="color_333333">#333333</color>
+    <color name="color_666666">#666666</color>
+    <color name="color_969696">#969696</color>
+    <color name="color_999999">#999999</color>
+    <color name="color_b2000000">#b2000000</color>
+    <color name="playmate_tabWidget_text_color">#969696</color>
+    <color name="navigation_bar_bg">@color/color_f9f9f9</color>
+    <color name="navigation_bar_line">@color/color_cccccc</color>
+    <color name="greed_color">#ff5215</color>
+    <color name="black_text_color">#000000</color>
+    <color name="color_dashen">#ff5215</color>
+    <color name="color_dashen_passed">#e54b12</color>
+    <color name="white_80_percent">#ccffffff</color>
+    <color name="white_33_percent">#33ffffff</color>
+    <color name="black_33_percent">#33000000</color>
+    <color name="white_66_percent">#66ffffff</color>
+    <color name="black_66_percent">#66000000</color>
+    <color name="item_select_color">#ff5215</color>
+    <color name="select_game_search_bg_color">#e6e6e6</color>
+    <color name="float_time_color">#f58220</color>
+    <color name="float_time_text_color">#bd6758</color>
+
+    <color name="color_e5e5e5">#e5e5e5</color>
+    <color name="color_f5f5f5">#f5f5f5</color>
+    <color name="color_f9f9f9">#f9f9f9</color>
+    <color name="color_cccccc">#cccccc</color>
+    <color name="c8c8c8">#c8c8c8</color>
+    <color name="transparent">#00000000</color>
+    <color name="bar_gray">#37474F</color>
+    <color name="while_bg">#F7F7F7</color>
+    <color name="ip_color_primary_dark">#303135</color>
+    <color name="ip_color_primary_dark_alpha">#66000000</color>
+    <color name="ic_back_press">#323336</color>
+    <color name="balloonperformer_translucent">#a0000000</color>
+
+    <color name="tab_color">#848282</color>
+    <color name="tab_color_s">#0dd21d</color>
+    <color name="send_bt_color">#0cb319</color>
+    <color name="line_color">#beb9b9</color>
+    <color name="decoration_color">#EDEDED</color>
+    <color name="action_bar_color">#0d0c0c</color>
+    <color name="tiem_bg_color">#f4cac7c7</color>
+    <color name="dot_color">#ff0000</color>
+</resources>

+ 8 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#6200EE</color>
+    <color name="colorPrimaryDark">#000000</color>
+    <color name="colorAccent">#03DAC5</color>
+
+    <color name="textInputLayout">#96ffffff</color>
+</resources>

+ 32 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="size_text">15sp</dimen>
+    <dimen name="setting_gray_size">12sp</dimen>
+    <dimen name="setting_item_height">50dp</dimen>
+    <dimen name="setting_start_padding">22dp</dimen>
+    <dimen name="setting_end_start_padding">25dp</dimen>
+    <dimen name="setting_end_padding">10dp</dimen>
+    <dimen name="setting_vertical_padding">5dp</dimen>
+    <dimen name="setting_start_font_width">150dp</dimen>
+    <dimen name="setting_end_font_width">60dp</dimen>
+    <dimen name="setting_start_image_width">20dp</dimen>
+    <dimen name="size_button">18sp</dimen>
+    <dimen name="btn_top_bottom_padding">10dp</dimen>
+    <dimen name="btn_height">50dp</dimen>
+    <dimen name="play_btn_width_height">38dp</dimen>
+
+    <dimen name="home_tab_height">70dp</dimen>
+    <dimen name="float_size">60dp</dimen>
+    <dimen name="float_margin_start">50dp</dimen>
+    <dimen name="float_stand_size">36dp</dimen>
+    <dimen name="float_logo_size">39dp</dimen>
+    <dimen name="float_logo_margin">43dp</dimen>
+    <dimen name="float_margin">7dp</dimen>
+    <dimen name="float_start_image_width">30dp</dimen>
+
+    <dimen name="guide_notification_icon_size">16dp</dimen>
+    <dimen name="guide_notification_width_size">260dp</dimen>
+
+    <dimen name="setting_start_font_size">13sp</dimen>
+    <dimen name="setting_end_font_size">13sp</dimen>
+</resources>

+ 0 - 0
app/src/main/res/values/strings.xml


部分文件因文件數量過多而無法顯示