git commit -m "first commit"
60
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.yameida.worktool">
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
<application
|
||||
android:name="org.yameida.worktool.MyApplication"
|
||||
android:allowBackup="true"
|
||||
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">
|
||||
|
||||
<activity
|
||||
android:name="org.yameida.worktool.activity.ListenActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/AppTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name="org.yameida.worktool.service.WeworkService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.accessibilityservice"
|
||||
android:resource="@xml/accessibility_service_config" />
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
16
app/src/main/java/org/yameida/worktool/Constant.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
package org.yameida.worktool
|
||||
|
||||
import com.blankj.utilcode.util.SPUtils
|
||||
import org.yameida.worktool.config.WebConfig
|
||||
|
||||
object Constant {
|
||||
|
||||
const val PACKAGE_NAMES = "com.tencent.wework"
|
||||
val BASE_URL = WebConfig.HOST.replace("wss", "https").replace("ws", "http")
|
||||
val URL_CHECK_UPDATE = "$BASE_URL/appUpdate/checkUpdate"
|
||||
|
||||
var key = "9876543210abcdef".toByteArray()
|
||||
var iv = "0123456789abcdef".toByteArray()
|
||||
val transformation = "AES/CBC/PKCS7Padding"
|
||||
var encryptType = SPUtils.getInstance().getInt("encryptType", 0)
|
||||
}
|
||||
121
app/src/main/java/org/yameida/worktool/Demo.kt
Normal file
@@ -0,0 +1,121 @@
|
||||
package org.yameida.worktool
|
||||
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
import org.yameida.worktool.service.MyLooper
|
||||
import org.yameida.worktool.service.WeworkController
|
||||
import org.yameida.worktool.service.WeworkLoopImpl
|
||||
import org.yameida.worktool.service.getRoot
|
||||
import org.yameida.worktool.utils.AccessibilityUtil
|
||||
|
||||
/**
|
||||
* 示例
|
||||
*/
|
||||
object Demo {
|
||||
|
||||
fun test(flag: Boolean) {
|
||||
if (!flag) return
|
||||
|
||||
MyLooper.getInstance().removeCallbacksAndMessages(null)
|
||||
|
||||
//打印当前视图树
|
||||
// AccessibilityUtil.printNodeClazzTree(getRoot())
|
||||
|
||||
//自动通过好友
|
||||
// WeworkLoopImpl.getFriendRequest()
|
||||
|
||||
//自动通过好友(后台可配置开关)
|
||||
// WeworkLoopImpl.mainLoop()
|
||||
|
||||
//创建群信息
|
||||
// WeworkController.initGroup(WeworkMessageBean().apply {
|
||||
// groupName = "新建外部群 " + UUID.randomUUID().toString().substring(0, 5)
|
||||
// selectList = arrayListOf("冯燕", "尹甲仑")
|
||||
// groupAnnouncement = "本群为雨花台区法院诉前调解官方微信群"
|
||||
// })
|
||||
|
||||
//修改群信息
|
||||
// WeworkController.intoGroupAndConfig(WeworkMessageBean().apply {
|
||||
// groupName = "新建外部群 " + UUID.randomUUID().toString().substring(0, 5)
|
||||
// selectList = arrayListOf("冯燕", "尹甲仑")
|
||||
// groupAnnouncement = "本群为雨花台区法院诉前调解官方微信群"
|
||||
// })
|
||||
|
||||
//获取群信息
|
||||
// WeworkController.getGroupInfo(WeworkMessageBean().apply {
|
||||
// selectList = arrayListOf("企微RPA机器人自测1")
|
||||
// })
|
||||
|
||||
//在房间内发送消息
|
||||
// WeworkController.sendMessage(WeworkMessageBean().apply {
|
||||
// titleList = arrayListOf("下级群1", "上级群1")
|
||||
// receivedContent = "aaa"
|
||||
// })
|
||||
|
||||
//获取我的信息
|
||||
// WeworkController.getMyInfo()
|
||||
|
||||
//推送任意小程序
|
||||
// WeworkController.pushMicroprogram(WeworkMessageBean().apply {
|
||||
// titleList = arrayListOf("尹甲仑")
|
||||
// objectName = "小法名律"
|
||||
// extraText = "123"
|
||||
// })
|
||||
|
||||
//推送微盘图片
|
||||
// WeworkController.pushMicroDiskImage(WeworkMessageBean().apply {
|
||||
// titleList = arrayListOf("尹甲仑")
|
||||
// objectName = "雨水.jpg"
|
||||
// })
|
||||
|
||||
//推送微盘文件
|
||||
// WeworkController.pushMicroDiskFile(WeworkMessageBean().apply {
|
||||
// titleList = arrayListOf("尹甲仑")
|
||||
// objectName = "雨水.jpg"
|
||||
// })
|
||||
|
||||
//推送腾讯文档
|
||||
// WeworkController.pushOffice(WeworkMessageBean().apply {
|
||||
// titleList = arrayListOf("尹甲仑")
|
||||
// objectName = "机器人中台"
|
||||
// extraText = "附加留言"
|
||||
// })
|
||||
}
|
||||
|
||||
//通过好友请求后执行三中院脚本
|
||||
fun test2(name: String) {
|
||||
val groupName = "(北)诉前调解群05011"
|
||||
val json = """
|
||||
{
|
||||
"socketType": 2,
|
||||
"messageId": "",
|
||||
"list": [
|
||||
{
|
||||
"type": 203,
|
||||
"titleList": [
|
||||
"$name"
|
||||
],
|
||||
"receivedContent": "您好,您好我是某某法院机器人助理,很高兴为您服务,请先填写个人信息,我们将为您联系案件相关法官。\nhttps://www.wjx.cn/vj/OjVAA02.aspx"
|
||||
},
|
||||
{
|
||||
"type": 203,
|
||||
"titleList": [
|
||||
"$name"
|
||||
],
|
||||
"receivedContent": "您好,已为您查询到本案法官,开始建群中,请稍后..."
|
||||
},
|
||||
{
|
||||
"type": 206,
|
||||
"groupName": "$groupName",
|
||||
"selectList": [
|
||||
"$name",
|
||||
"尹甲仑"
|
||||
],
|
||||
"groupAnnouncement": "本群为诉前调解官方微信群"
|
||||
}
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
MyLooper.onMessage(null, json)
|
||||
}
|
||||
|
||||
}
|
||||
28
app/src/main/java/org/yameida/worktool/MyApplication.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
package org.yameida.worktool
|
||||
|
||||
import android.app.Application
|
||||
import com.blankj.utilcode.util.SPUtils
|
||||
import com.blankj.utilcode.util.Utils
|
||||
import com.umeng.commonsdk.UMConfigure
|
||||
import org.yameida.worktool.config.GlobalException
|
||||
import update.UpdateAppUtils
|
||||
|
||||
class MyApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
//初始化工具类
|
||||
Utils.init(this)
|
||||
//初始化友盟统计
|
||||
UMConfigure.preInit(this, "6284a3a3d024421570f97c3c", "main_channel")
|
||||
//判断是否同意隐私协议,uminit为1时为已经同意,直接初始化umsdk
|
||||
if (SPUtils.getInstance().getString("uminit", "1") == "1") {
|
||||
UMConfigure.init(this, "6284a3a3d024421570f97c3c", "main_channel", UMConfigure.DEVICE_TYPE_PHONE, "")
|
||||
}
|
||||
//初始化自动更新
|
||||
UpdateAppUtils.init(this)
|
||||
//设置全局异常捕获重启
|
||||
Thread.setDefaultUncaughtExceptionHandler(GlobalException.getInstance())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package org.yameida.worktool.activity
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.text.TextUtils.SimpleStringSplitter
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.Switch
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.blankj.utilcode.util.*
|
||||
import com.umeng.analytics.MobclickAgent
|
||||
import kotlinx.android.synthetic.main.activity_listen.*
|
||||
import org.yameida.worktool.*
|
||||
import org.yameida.worktool.service.WeworkService
|
||||
import org.yameida.worktool.config.WebConfig
|
||||
import org.yameida.worktool.utils.UpdateUtil
|
||||
|
||||
class ListenActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
title = "WorkTool"
|
||||
setContentView(R.layout.activity_listen)
|
||||
|
||||
initView()
|
||||
initAccessibility()
|
||||
UpdateUtil.checkUpdate()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
freshOpenServiceSwitch(
|
||||
WeworkService::class.java,
|
||||
sw_accessibility
|
||||
)
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
et_channel.setText(SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID))
|
||||
bt_save.setOnClickListener {
|
||||
val channel = et_channel.text.toString().trim()
|
||||
SPUtils.getInstance().put(WebConfig.LISTEN_CHANNEL_ID, channel)
|
||||
ToastUtils.showLong("保存成功")
|
||||
sendBroadcast(Intent(WebConfig.WEWORK_NOTIFY).apply {
|
||||
putExtra("type", "modify_channel")
|
||||
})
|
||||
MobclickAgent.onProfileSignIn(channel)
|
||||
}
|
||||
Constant.encryptType = SPUtils.getInstance().getInt("encryptType", 0)
|
||||
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)
|
||||
})
|
||||
tv_host.text = WebConfig.HOST
|
||||
tv_version.text = AppUtils.getAppVersionName()
|
||||
}
|
||||
|
||||
private fun initAccessibility() {
|
||||
sw_accessibility.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
|
||||
LogUtils.i("sw_accessibility onCheckedChanged: $isChecked")
|
||||
if (isChecked) {
|
||||
if (SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID).isNullOrBlank()) {
|
||||
sw_accessibility.isChecked = false
|
||||
ToastUtils.showLong("请先填写并保存链接号~")
|
||||
} else if (!isAccessibilitySettingOn()) {
|
||||
openAccessibility()
|
||||
}
|
||||
} else {
|
||||
if (isAccessibilitySettingOn()) {
|
||||
sw_accessibility.isChecked = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun isAccessibilitySettingOn(): Boolean {
|
||||
val context = Utils.getApp()
|
||||
var enable = 0
|
||||
val serviceName = context.packageName + "/" + WeworkService::class.java.canonicalName
|
||||
LogUtils.i("isAccessibilitySettingOn: $serviceName")
|
||||
try {
|
||||
enable = Settings.Secure.getInt(
|
||||
context.contentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_ENABLED,
|
||||
0
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
if (enable == 1) {
|
||||
val stringSplitter = 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) {
|
||||
LogUtils.i("isAccessibilitySettingOn: true")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LogUtils.i("isAccessibilitySettingOn: false")
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开辅助
|
||||
*/
|
||||
private fun openAccessibility() {
|
||||
val clickListener =
|
||||
DialogInterface.OnClickListener { dialog, which ->
|
||||
freshOpenServiceSwitch(
|
||||
WeworkService::class.java,
|
||||
sw_accessibility
|
||||
)
|
||||
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
|
||||
startActivity(intent)
|
||||
}
|
||||
val cancel = DialogInterface.OnCancelListener {
|
||||
freshOpenServiceSwitch(
|
||||
WeworkService::class.java,
|
||||
sw_accessibility
|
||||
)
|
||||
}
|
||||
val cancelListener = DialogInterface.OnClickListener { dialog, which ->
|
||||
freshOpenServiceSwitch(
|
||||
WeworkService::class.java,
|
||||
sw_accessibility
|
||||
)
|
||||
}
|
||||
val dialog: AlertDialog = AlertDialog.Builder(this)
|
||||
.setMessage(R.string.tips)
|
||||
.setOnCancelListener(cancel)
|
||||
.setNegativeButton("取消", cancelListener)
|
||||
.setPositiveButton("确定", clickListener)
|
||||
.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun freshOpenServiceSwitch(clazz: Class<*>, s: Switch) {
|
||||
if (isAccessibilitySettingOn()) {
|
||||
s.isChecked = true
|
||||
when (s.id) {
|
||||
R.id.sw_accessibility -> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.isChecked = false
|
||||
when (s.id) {
|
||||
R.id.sw_accessibility -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.yameida.worktool.config;
|
||||
public class WebConfig {
|
||||
public static final String WEWORK_NOTIFY = "wework_notify";
|
||||
|
||||
public static final String HOST = "wss://worktool.asrtts.cn";
|
||||
public static final String WEWORK_URL = HOST + "/webserver/wework/";
|
||||
public static String LISTEN_CHANNEL_ID = "LISTEN_CHANNEL_ID";
|
||||
}
|
||||
162
app/src/main/java/org/yameida/worktool/model/AppUpdate.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package org.yameida.worktool.model;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class WeworkMessageBean {
|
||||
|
||||
/**
|
||||
* type
|
||||
* <p>
|
||||
*消息类型10
|
||||
* 心跳 HEART_BEAT
|
||||
* <p>
|
||||
* 消息类型 100
|
||||
* 上传消息列表 TYPE_RECEIVE_MESSAGE_LIST
|
||||
* <p>
|
||||
* 全局操作类型 200
|
||||
* 停止所有任务并返回首页待命 STOP_AND_GO_HOME
|
||||
* 回到首页等待接收新消息 LOOP_RECEIVE_NEW_MESSAGE
|
||||
* 在房间内发送消息 SEND_MESSAGE
|
||||
* 在房间内指定回复消息 REPLY_MESSAGE
|
||||
* 在房间内转发消息 RELAY_MESSAGE
|
||||
* 创建群 CREATE_GROUP
|
||||
* 进入群聊并修改群配置 INTO_GROUP_AND_CONFIG
|
||||
* 推送微盘图片 PUSH_MICRO_DISK_IMAGE
|
||||
* 推送微盘文件 PUSH_MICRO_DISK_FILE
|
||||
* 推送任意小程序 PUSH_MICROPROGRAM
|
||||
* 推送腾讯文档 PUSH_OFFICE
|
||||
* 通过当前所有好友请求 PASS_ALL_FRIEND_REQUEST
|
||||
* 按手机号添加好友 ADD_FRIEND_BY_PHONE
|
||||
* 展示群信息 SHOW_GROUP_INFO
|
||||
* <p>
|
||||
* 非操作类型 300
|
||||
* 机器人普通日志记录 ROBOT_LOG
|
||||
* 机器人异常日志记录 ROBOT_ERROR_LOG
|
||||
* 机器人接口测试 ROBOT_CONTROLLER_TEST
|
||||
* <p>
|
||||
* 获取数据类型 500
|
||||
* 获取群信息 GET_GROUP_INFO
|
||||
* 获取好友信息 GET_FRIEND_INFO
|
||||
* 获取我的信息 GET_MY_INFO
|
||||
*/
|
||||
public static final int HEART_BEAT = 11;
|
||||
public static final int TYPE_RECEIVE_MESSAGE_LIST = 101;
|
||||
|
||||
public static final int STOP_AND_GO_HOME = 201;
|
||||
public static final int LOOP_RECEIVE_NEW_MESSAGE = 202;
|
||||
public static final int SEND_MESSAGE = 203;
|
||||
public static final int REPLY_MESSAGE = 204;
|
||||
public static final int RELAY_MESSAGE = 205;
|
||||
public static final int INIT_GROUP = 206;
|
||||
public static final int INTO_GROUP_AND_CONFIG = 207;
|
||||
public static final int PUSH_MICRO_DISK_IMAGE = 208;
|
||||
public static final int PUSH_MICRO_DISK_FILE = 209;
|
||||
public static final int PUSH_MICROPROGRAM = 210;
|
||||
public static final int PUSH_OFFICE = 211;
|
||||
public static final int PASS_ALL_FRIEND_REQUEST = 212;
|
||||
public static final int ADD_FRIEND_BY_PHONE = 213;
|
||||
public static final int SHOW_GROUP_INFO = 214;
|
||||
|
||||
public static final int ROBOT_LOG = 301;
|
||||
public static final int ROBOT_ERROR_LOG = 302;
|
||||
public static final int ROBOT_CONTROLLER_TEST = 303;
|
||||
|
||||
public static final int GET_GROUP_INFO = 501;
|
||||
public static final int GET_FRIEND_INFO = 502;
|
||||
public static final int GET_MY_INFO = 503;
|
||||
|
||||
/**
|
||||
* roomType
|
||||
* <p>
|
||||
* 外部群 ROOM_TYPE_EXTERNAL_GROUP
|
||||
* 外部联系人 ROOM_TYPE_EXTERNAL_CONTACT
|
||||
* 内部群 ROOM_TYPE_INTERNAL_GROUP
|
||||
* 内部联系人 ROOM_TYPE_INTERNAL_CONTACT
|
||||
*/
|
||||
public static final int ROOM_TYPE = 0;
|
||||
public static final int ROOM_TYPE_UNKNOWN = 0;
|
||||
public static final int ROOM_TYPE_EXTERNAL_GROUP = 1;
|
||||
public static final int ROOM_TYPE_EXTERNAL_CONTACT = 2;
|
||||
public static final int ROOM_TYPE_INTERNAL_GROUP = 3;
|
||||
public static final int ROOM_TYPE_INTERNAL_CONTACT = 4;
|
||||
|
||||
/**
|
||||
* textType
|
||||
* <p>
|
||||
* 文本类型 TEXT_TYPE_PLAIN
|
||||
* 表情类型 同文本类型
|
||||
* 群公告类型 同文本类型
|
||||
* 图片类型 TEXT_TYPE_IMAGE
|
||||
* 语音类型 TEXT_TYPE_VOICE
|
||||
* 名片类型 TEXT_TYPE_CARD
|
||||
* 视频类型 TEXT_TYPE_VIDEO
|
||||
* 定位类型 TEXT_TYPE_LOCATION
|
||||
* 小程序类型 TEXT_TYPE_MICROPROGRAM
|
||||
* 链接类型 TEXT_TYPE_LINK
|
||||
* 群通知类型 同链接类型
|
||||
* 文件类型 TEXT_TYPE_FILE
|
||||
* 警告类型 TEXT_TYPE_WARNING
|
||||
* 腾讯文档类型 TEXT_TYPE_OFFICE
|
||||
* 群接龙类型 TEXT_TYPE_SOLITAIRE
|
||||
* 合并聊天记录类型 TEXT_TYPE_CHAT_RECORD
|
||||
* 群收集表类型 TEXT_TYPE_COLLECTION
|
||||
* 接收带回复引用文本类型 TEXT_TYPE_REPLY
|
||||
*/
|
||||
public static final int TEXT_TYPE = 0;
|
||||
public static final int TEXT_TYPE_UNKNOWN = 0;
|
||||
public static final int TEXT_TYPE_PLAIN = 1;
|
||||
public static final int TEXT_TYPE_IMAGE = 2;
|
||||
public static final int TEXT_TYPE_VOICE = 3;
|
||||
public static final int TEXT_TYPE_CARD = 4;
|
||||
public static final int TEXT_TYPE_VIDEO = 5;
|
||||
public static final int TEXT_TYPE_LOCATION = 6;
|
||||
public static final int TEXT_TYPE_MICROPROGRAM = 7;
|
||||
public static final int TEXT_TYPE_LINK = 8;
|
||||
public static final int TEXT_TYPE_FILE = 9;
|
||||
public static final int TEXT_TYPE_WARNING = 10;
|
||||
public static final int TEXT_TYPE_OFFICE = 11;
|
||||
public static final int TEXT_TYPE_SOLITAIRE = 12;
|
||||
public static final int TEXT_TYPE_CHAT_RECORD = 13;
|
||||
public static final int TEXT_TYPE_COLLECTION = 14;
|
||||
public static final int TEXT_TYPE_REPLY = 15;
|
||||
|
||||
//标题 通常是群名或联系人
|
||||
public List<String> titleList;
|
||||
//上传聊天列表
|
||||
public List<SubMessageBean> messageList;
|
||||
//上传日志内容
|
||||
public String log;
|
||||
//外部群1 外部联系人2 内部群3 内部联系人4
|
||||
public Integer roomType;
|
||||
|
||||
//接收人名称
|
||||
public String receivedName;
|
||||
//内容移除了@me
|
||||
public String receivedContent;
|
||||
//原始内容text
|
||||
public String originalContent;
|
||||
//多选(转发等)
|
||||
public List<String> nameList;
|
||||
//转发附加留言
|
||||
public String extraText;
|
||||
//接收消息类型
|
||||
public int textType;
|
||||
|
||||
//群名
|
||||
public String groupName;
|
||||
//群主名称
|
||||
public String groupOwner;
|
||||
//成员名单
|
||||
public List<String> selectList;
|
||||
//成员数
|
||||
public Integer groupNumber;
|
||||
//群公告
|
||||
public String groupAnnouncement;
|
||||
//新群名
|
||||
public String newGroupName;
|
||||
//新群公告
|
||||
public String newGroupAnnouncement;
|
||||
//踢人列表
|
||||
public List<String> removeList;
|
||||
//拉人是否附带历史记录
|
||||
public boolean showMessageHistory = false;
|
||||
//我的信息
|
||||
public MyInfo myInfo;
|
||||
//对象名称(图片、文件、小程序等)
|
||||
public String objectName;
|
||||
|
||||
public WeworkMessageBean() {}
|
||||
|
||||
public WeworkMessageBean(String receivedName, String receivedContent, int type, Integer roomType, List<String> titleList, List<SubMessageBean> messageList, String log) {
|
||||
this.type = type;
|
||||
this.roomType = roomType;
|
||||
this.titleList = titleList;
|
||||
this.messageList = messageList;
|
||||
this.log = log;
|
||||
this.receivedContent = receivedContent;
|
||||
this.receivedName = receivedName;
|
||||
}
|
||||
|
||||
public int type = 0;
|
||||
|
||||
//消息列表的每条消息
|
||||
public static class SubMessageBean {
|
||||
//0其他人 1机器人自己 2unknown(如系统消息)
|
||||
public int sender = 0;
|
||||
//消息类型判断 仅针对sender=0
|
||||
public int textType;
|
||||
public List<ItemMessageBean> itemMessageList;
|
||||
public List<String> nameList;
|
||||
|
||||
public SubMessageBean(int sender, int textType, List<ItemMessageBean> itemMessageList, List<String> nameList) {
|
||||
this.sender = sender;
|
||||
this.textType = textType;
|
||||
this.itemMessageList = itemMessageList;
|
||||
this.nameList = nameList;
|
||||
}
|
||||
}
|
||||
|
||||
//消息列表每条消息的text推断
|
||||
public static class ItemMessageBean {
|
||||
//0消息主体上方信息 如日期等 系统消息(拉人/撤回/外部群等居中的提示语)
|
||||
//2消息内容
|
||||
public int feature = 0;
|
||||
public String text;
|
||||
|
||||
public ItemMessageBean(int feature, String text) {
|
||||
this.feature = feature;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
//我的信息
|
||||
public static class MyInfo {
|
||||
//{姓名=企微RPA机器人, 工作签名=添加工作签名…, 手机=17326101105, 别名=企微RPA机器人, 对外信息显示=企微RPA机器人@擎盾数据, 职务=企微RPA机器人, 所在企业=TEST 擎盾数据, 性别=男}
|
||||
public String name;
|
||||
public String alias;
|
||||
public String gender;
|
||||
public String showName;
|
||||
public String workSign;
|
||||
public String corporation;
|
||||
public String phone;
|
||||
public String job;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
WeworkMessageBean that = (WeworkMessageBean) o;
|
||||
return textType == that.textType && showMessageHistory == that.showMessageHistory && type == that.type && Objects.equals(titleList, that.titleList) && Objects.equals(messageList, that.messageList) && Objects.equals(log, that.log) && Objects.equals(roomType, that.roomType) && Objects.equals(receivedName, that.receivedName) && Objects.equals(receivedContent, that.receivedContent) && Objects.equals(originalContent, that.originalContent) && Objects.equals(nameList, that.nameList) && Objects.equals(extraText, that.extraText) && Objects.equals(groupName, that.groupName) && Objects.equals(groupOwner, that.groupOwner) && Objects.equals(selectList, that.selectList) && Objects.equals(groupNumber, that.groupNumber) && Objects.equals(groupAnnouncement, that.groupAnnouncement) && Objects.equals(newGroupName, that.newGroupName) && Objects.equals(newGroupAnnouncement, that.newGroupAnnouncement) && Objects.equals(removeList, that.removeList) && Objects.equals(myInfo, that.myInfo) && Objects.equals(objectName, that.objectName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(titleList, messageList, log, roomType, receivedName, receivedContent, originalContent, nameList, extraText, textType, groupName, groupOwner, selectList, groupNumber, groupAnnouncement, newGroupName, newGroupAnnouncement, removeList, showMessageHistory, myInfo, objectName, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WeworkMessageBean{" +
|
||||
"titleList=" + titleList +
|
||||
", messageList=" + messageList +
|
||||
", log='" + log + '\'' +
|
||||
", roomType=" + roomType +
|
||||
", receivedName='" + receivedName + '\'' +
|
||||
", receivedContent='" + receivedContent + '\'' +
|
||||
", originalContent='" + originalContent + '\'' +
|
||||
", nameList=" + nameList +
|
||||
", extraText='" + extraText + '\'' +
|
||||
", textType=" + textType +
|
||||
", groupName='" + groupName + '\'' +
|
||||
", groupOwner='" + groupOwner + '\'' +
|
||||
", selectList=" + selectList +
|
||||
", groupNumber=" + groupNumber +
|
||||
", groupAnnouncement='" + groupAnnouncement + '\'' +
|
||||
", newGroupName='" + newGroupName + '\'' +
|
||||
", newGroupAnnouncement='" + newGroupAnnouncement + '\'' +
|
||||
", removeList=" + removeList +
|
||||
", showMessageHistory=" + showMessageHistory +
|
||||
", myInfo=" + myInfo +
|
||||
", objectName='" + objectName + '\'' +
|
||||
", type=" + type +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class WeworkMessageListBean {
|
||||
|
||||
companion object {
|
||||
const val SOCKET_TYPE_HEARTBEAT = 0
|
||||
const val SOCKET_TYPE_MESSAGE_CONFIRM = 1
|
||||
const val SOCKET_TYPE_MESSAGE_LIST = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* type
|
||||
* TYPE_HEARTBEAT 心跳检测
|
||||
* TYPE_MESSAGE_CONFIRM 消息确认
|
||||
* TYPE_MESSAGE_LIST 消息列表
|
||||
*/
|
||||
var socketType = SOCKET_TYPE_HEARTBEAT
|
||||
|
||||
//消息id
|
||||
var messageId = TimeUtils.date2String(Date()).replace(" ", "#") + "#" + UUID.randomUUID()
|
||||
|
||||
//消息列表
|
||||
var list: ArrayList<WeworkMessageBean> = arrayListOf()
|
||||
|
||||
//加密消息列表
|
||||
var encryptedList: String = ""
|
||||
|
||||
//消息加密 0不加密 1AES
|
||||
var encryptType = Constant.encryptType
|
||||
|
||||
constructor(weworkMessageBean: WeworkMessageBean, type: Int) {
|
||||
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
|
||||
}
|
||||
|
||||
constructor(messageId: String, type: Int) {
|
||||
this.messageId = messageId
|
||||
this.socketType = type
|
||||
}
|
||||
|
||||
constructor(weworkMessageBeanList: List<WeworkMessageBean>, type: Int) {
|
||||
if (encryptType == 0) {
|
||||
list.addAll(weworkMessageBeanList)
|
||||
} else if (encryptType == 1) {
|
||||
encryptedList = EncryptUtils.encryptAES2HexString(
|
||||
GsonUtils.toJson(weworkMessageBeanList).toByteArray(),
|
||||
Constant.key,
|
||||
Constant.transformation,
|
||||
Constant.iv
|
||||
)
|
||||
}
|
||||
this.socketType = type
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
156
app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt
Normal file
@@ -0,0 +1,156 @@
|
||||
package org.yameida.worktool.service
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import com.blankj.utilcode.util.GsonUtils
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import org.yameida.worktool.Constant
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
import org.yameida.worktool.model.WeworkMessageListBean
|
||||
import org.yameida.worktool.utils.AccessibilityUtil
|
||||
import org.yameida.worktool.utils.Views
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* 进入首页-消息页
|
||||
*/
|
||||
fun goHome() {
|
||||
goHomeTab("消息")
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入首页tab
|
||||
* 1.检查是否有底部tab
|
||||
* 2.回退到首页
|
||||
* @param title 消息/文档/通讯录/工作台/我
|
||||
* 可能因为管理员排版首页Tab而导致找不到匹配title
|
||||
*/
|
||||
fun goHomeTab(title: String): Boolean {
|
||||
var atHome = false
|
||||
var find = false
|
||||
while (!atHome) {
|
||||
val list = getRoot().findAccessibilityNodeInfosByText("消息")
|
||||
for (item in list) {
|
||||
if (item.parent.parent.parent.childCount == 5) {
|
||||
atHome = true
|
||||
val tempList = getRoot().findAccessibilityNodeInfosByText(title)
|
||||
for (tempItem in tempList) {
|
||||
if (tempItem.parent.parent.parent.childCount == 5) {
|
||||
AccessibilityUtil.performClick(tempItem)
|
||||
sleep(300)
|
||||
find = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!atHome) {
|
||||
backPress()
|
||||
sleep(1500)
|
||||
}
|
||||
}
|
||||
LogUtils.v("进入首页-${title}页")
|
||||
return find
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前是否在首页
|
||||
*/
|
||||
fun isAtHome(): Boolean {
|
||||
val list = getRoot().findAccessibilityNodeInfosByText("消息")
|
||||
return list.count { it.parent.parent.parent.childCount == 5 } > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取企业微信窗口
|
||||
*/
|
||||
fun getRoot(): AccessibilityNodeInfo {
|
||||
return getRoot(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前台窗口
|
||||
* @param ignoreCheck false 必须等待前台为企业微信 true 直接返回当前前台窗口
|
||||
*/
|
||||
fun getRoot(ignoreCheck: Boolean): AccessibilityNodeInfo {
|
||||
while (true) {
|
||||
val tempRoot = WeworkController.weworkService.rootInActiveWindow
|
||||
val root = WeworkController.weworkService.rootInActiveWindow
|
||||
if (tempRoot != root) {
|
||||
LogUtils.e("tempRoot != root")
|
||||
}
|
||||
if (root != null) {
|
||||
if (root.packageName == Constant.PACKAGE_NAMES) {
|
||||
return root
|
||||
} else {
|
||||
LogUtils.e("当前不在企业微信: ${root.packageName}")
|
||||
error("当前不在企业微信: ${root.packageName}")
|
||||
if (ignoreCheck) {
|
||||
return root
|
||||
}
|
||||
}
|
||||
}
|
||||
sleep(1000)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后退
|
||||
*/
|
||||
fun backPress() {
|
||||
val textView = AccessibilityUtil.findOneByClazz(getRoot(), Views.TextView)
|
||||
if (textView != null && textView.text.isNullOrBlank()) {
|
||||
LogUtils.d("找到回退按钮")
|
||||
AccessibilityUtil.performClick(textView)
|
||||
} else {
|
||||
val ivButton = AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView)
|
||||
if (ivButton != null && ivButton.isClickable && AccessibilityUtil.findFrontNode(ivButton) == null) {
|
||||
LogUtils.d("未找到回退按钮 点击第一个IV按钮")
|
||||
AccessibilityUtil.performClick(ivButton)
|
||||
} else {
|
||||
LogUtils.d("未找到回退按钮 点击第一个BT按钮")
|
||||
val button = AccessibilityUtil.findOneByClazz(getRoot(), Views.Button)
|
||||
if (button != null && button.childCount > 0) {
|
||||
AccessibilityUtil.performClick(button.getChild(0))
|
||||
} else {
|
||||
AccessibilityUtil.performClick(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
sleep(1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传运行日志 简单封装 info log
|
||||
*/
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传运行日志 简单封装 error log
|
||||
*/
|
||||
fun error(message: Any?) {
|
||||
log(message, WeworkMessageBean.ROBOT_ERROR_LOG)
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单封装 sleep
|
||||
*/
|
||||
fun sleep(time: Long) {
|
||||
try {
|
||||
Thread.sleep(time)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
155
app/src/main/java/org/yameida/worktool/service/MyLooper.kt
Normal file
@@ -0,0 +1,155 @@
|
||||
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.google.gson.reflect.TypeToken
|
||||
import okhttp3.WebSocket
|
||||
import org.yameida.worktool.Constant
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
import org.yameida.worktool.model.WeworkMessageListBean
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.LinkedHashSet
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
object MyLooper {
|
||||
|
||||
private var threadHandler: Handler? = null
|
||||
|
||||
val looper = thread {
|
||||
LogUtils.e("myLooper starting...")
|
||||
Looper.prepare()
|
||||
val myLooper = Looper.myLooper()
|
||||
if (myLooper != null) {
|
||||
threadHandler = object : Handler(myLooper) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
LogUtils.d("handle message: " + Thread.currentThread().name, msg)
|
||||
try {
|
||||
dealWithMessage(msg.obj as WeworkMessageBean)
|
||||
} catch (e: Exception) {
|
||||
LogUtils.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("myLooper is null!")
|
||||
}
|
||||
Looper.loop()
|
||||
}
|
||||
|
||||
fun init() {}
|
||||
|
||||
fun getInstance(): Handler {
|
||||
while (true) {
|
||||
threadHandler?.let { return it }
|
||||
LogUtils.e("threadHandler is not ready...")
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessage(webSocket: WebSocket?, text: String) {
|
||||
val messageList =
|
||||
GsonUtils.fromJson<WeworkMessageListBean>(text, WeworkMessageListBean::class.java)
|
||||
if (messageList.socketType == WeworkMessageListBean.SOCKET_TYPE_HEARTBEAT) {
|
||||
return
|
||||
}
|
||||
if (messageList.socketType == WeworkMessageListBean.SOCKET_TYPE_MESSAGE_CONFIRM) {
|
||||
return
|
||||
}
|
||||
if (messageList.socketType == WeworkMessageListBean.SOCKET_TYPE_MESSAGE_LIST) {
|
||||
val confirm = WeworkController.weworkService.webSocketManager.confirm(messageList.messageId)
|
||||
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
|
||||
)
|
||||
}
|
||||
//去重处理 丢弃之前的重复指令 丢弃之前的获取新消息指令
|
||||
for (message in LinkedHashSet(messageList.list)) {
|
||||
getInstance().removeMessages(WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE)
|
||||
if (message.type == WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE) {
|
||||
if (!WeworkController.mainLoopRunning) {
|
||||
getInstance().sendMessage(Message.obtain().apply {
|
||||
what = WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
|
||||
obj = message
|
||||
})
|
||||
}
|
||||
} else {
|
||||
WeworkController.mainLoopRunning = false
|
||||
getInstance().removeMessages(message.type * message.hashCode())
|
||||
getInstance().sendMessage(Message.obtain().apply {
|
||||
what = message.type * message.hashCode()
|
||||
obj = message
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dealWithMessage(message: WeworkMessageBean) {
|
||||
when (message.type) {
|
||||
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.INTO_GROUP_AND_CONFIG -> {
|
||||
WeworkController.intoGroupAndConfig(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 -> {
|
||||
}
|
||||
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()
|
||||
}
|
||||
WeworkMessageBean.ROBOT_CONTROLLER_TEST -> {
|
||||
WeworkController.test(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package org.yameida.worktool.service
|
||||
|
||||
import com.blankj.utilcode.util.*
|
||||
import org.yameida.worktool.Demo
|
||||
import org.yameida.worktool.annotation.RequestMapping
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
|
||||
/**
|
||||
* 企业微信客服端反转
|
||||
* 被服务端远程调用的服务Controller类
|
||||
*/
|
||||
object WeworkController {
|
||||
|
||||
lateinit var weworkService: WeworkService
|
||||
var mainLoopRunning = false
|
||||
|
||||
/**
|
||||
* 停止所有任务并返回首页待命
|
||||
* @see WeworkMessageBean.STOP_AND_GO_HOME
|
||||
*/
|
||||
@RequestMapping
|
||||
fun stopAndGoHome() {
|
||||
LogUtils.d("stopAndGoHome()")
|
||||
mainLoopRunning = false
|
||||
goHome()
|
||||
}
|
||||
|
||||
/**
|
||||
* 回到首页等待接收新消息
|
||||
* @see WeworkMessageBean.LOOP_RECEIVE_NEW_MESSAGE
|
||||
*/
|
||||
@RequestMapping
|
||||
fun loopReceiveNewMessage() {
|
||||
LogUtils.d("loopReceiveNewMessage()")
|
||||
WeworkLoopImpl.mainLoop()
|
||||
}
|
||||
|
||||
/**
|
||||
* 在房间内发送消息
|
||||
* @see WeworkMessageBean.SEND_MESSAGE
|
||||
* @param message#titleList 房间名称
|
||||
* @param message#receivedContent 回复内容
|
||||
* @see WeworkMessageBean.TEXT_TYPE
|
||||
*/
|
||||
@RequestMapping
|
||||
fun sendMessage(message: WeworkMessageBean): Boolean {
|
||||
LogUtils.d("sendMessage(): ${message.titleList} ${message.receivedContent}")
|
||||
return WeworkOperationImpl.sendMessage(message.titleList, message.receivedContent)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在房间内指定回复消息
|
||||
* @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("replyMessage(): ${message.receivedContent}")
|
||||
return WeworkOperationImpl.replyMessage(
|
||||
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("relayMessage(): ${message.titleList} ${message.receivedName} ${message.originalContent} ${message.textType} ${message.nameList} ${message.extraText}")
|
||||
return WeworkOperationImpl.relayMessage(
|
||||
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 修改群公告 选填
|
||||
*/
|
||||
@RequestMapping
|
||||
fun initGroup(message: WeworkMessageBean): Boolean {
|
||||
LogUtils.d("initGroup(): ${message.groupName} ${message.selectList} ${message.groupAnnouncement}")
|
||||
return WeworkOperationImpl.initGroup(
|
||||
message.groupName,
|
||||
message.selectList,
|
||||
message.groupAnnouncement
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人接口测试
|
||||
* @see WeworkMessageBean.ROBOT_CONTROLLER_TEST
|
||||
*/
|
||||
@RequestMapping
|
||||
fun test(message: WeworkMessageBean? = null) {
|
||||
LogUtils.d(message)
|
||||
Demo.test(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入群聊并修改群配置
|
||||
* 群名称、群公告、拉人、踢人
|
||||
* @see WeworkMessageBean.INTO_GROUP_AND_CONFIG
|
||||
* @param message#groupName 待修改的群
|
||||
* @param message#newGroupName 修改群名 选填
|
||||
* @param message#newGroupAnnouncement 修改群公告 选填
|
||||
* @param message#selectList 添加群成员名称列表/拉人 选填
|
||||
* @param message#showMessageHistory 拉人是否附带历史记录 选填
|
||||
* @param message#removeList 移除群成员名称列表/踢人 选填
|
||||
*/
|
||||
@RequestMapping
|
||||
fun intoGroupAndConfig(message: WeworkMessageBean): Boolean {
|
||||
LogUtils.d("intoGroupAndConfig(): ${message.groupName} ${message.newGroupName} ${message.newGroupAnnouncement} ${message.selectList} ${message.showMessageHistory} ${message.removeList}")
|
||||
return WeworkOperationImpl.intoGroupAndConfig(
|
||||
message.groupName,
|
||||
message.newGroupName,
|
||||
message.newGroupAnnouncement,
|
||||
message.selectList,
|
||||
message.showMessageHistory,
|
||||
message.removeList
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送微盘图片
|
||||
* @see WeworkMessageBean.PUSH_MICRO_DISK_IMAGE
|
||||
* @param message#titleList 待发送姓名列表
|
||||
* @param message#objectName 图片名称
|
||||
* @param message#extraText 附加留言 可选
|
||||
*/
|
||||
@RequestMapping
|
||||
fun pushMicroDiskImage(message: WeworkMessageBean): Boolean {
|
||||
LogUtils.d("pushMicroDiskImage(): ${message.titleList} ${message.objectName} ${message.extraText}")
|
||||
return WeworkOperationImpl.pushMicroDiskImage(
|
||||
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("pushMicroDiskFile(): ${message.titleList} ${message.objectName} ${message.extraText}")
|
||||
return WeworkOperationImpl.pushMicroDiskFile(
|
||||
message.titleList,
|
||||
message.objectName,
|
||||
message.extraText
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送任意小程序
|
||||
* @see WeworkMessageBean.PUSH_MICROPROGRAM
|
||||
* @param message#titleList 待发送姓名列表
|
||||
* @param message#objectName 小程序名称
|
||||
* @param message#extraText 附加留言 可选
|
||||
*/
|
||||
@RequestMapping
|
||||
fun pushMicroprogram(message: WeworkMessageBean): Boolean {
|
||||
LogUtils.d("pushMicroprogram(): ${message.titleList} ${message.objectName} ${message.extraText}")
|
||||
return WeworkOperationImpl.pushMicroprogram(
|
||||
message.titleList,
|
||||
message.objectName,
|
||||
message.extraText
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送腾讯文档
|
||||
* @see WeworkMessageBean.PUSH_OFFICE
|
||||
* TODO 自己的文档分享时可选择权限级别
|
||||
* @param message#titleList 待发送姓名列表
|
||||
* @param message#objectName 腾讯文档名称
|
||||
* @param message#extraText 附加留言 可选
|
||||
*/
|
||||
@RequestMapping
|
||||
fun pushOffice(message: WeworkMessageBean): Boolean {
|
||||
LogUtils.d("pushOffice(): ${message.titleList} ${message.objectName} ${message.extraText}")
|
||||
return WeworkOperationImpl.pushOffice(
|
||||
message.titleList,
|
||||
message.objectName,
|
||||
message.extraText
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示群信息
|
||||
* @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("showGroupInfo(): ${message.titleList} ${message.receivedName} ${message.originalContent} ${message.textType}")
|
||||
return WeworkOperationImpl.showGroupInfo(
|
||||
message.titleList,
|
||||
message.receivedName,
|
||||
message.originalContent,
|
||||
message.textType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群信息
|
||||
* @see WeworkMessageBean.GET_GROUP_INFO
|
||||
* @param message#selectList 群名列表 为空时去群管理页查询并返回群聊页
|
||||
*/
|
||||
@RequestMapping
|
||||
fun getGroupInfo(message: WeworkMessageBean): Boolean {
|
||||
LogUtils.d("getGroupInfo(): ${message.selectList}")
|
||||
return WeworkGetImpl.getGroupInfo(message.selectList)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友信息
|
||||
* @see WeworkMessageBean.GET_FRIEND_INFO
|
||||
* TODO
|
||||
* @param message#selectList 好友名列表
|
||||
*/
|
||||
@RequestMapping
|
||||
fun getFriendInfo(message: WeworkMessageBean): Boolean {
|
||||
LogUtils.d("getFriendInfo(): ${message.selectList}")
|
||||
return WeworkGetImpl.getFriendInfo(message.selectList)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取我的信息
|
||||
* @see WeworkMessageBean.GET_MY_INFO
|
||||
*/
|
||||
@RequestMapping
|
||||
fun getMyInfo(): Boolean {
|
||||
LogUtils.d("getMyInfo():")
|
||||
return WeworkGetImpl.getMyInfo()
|
||||
}
|
||||
|
||||
}
|
||||
149
app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt
Normal file
@@ -0,0 +1,149 @@
|
||||
package org.yameida.worktool.service
|
||||
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
import org.yameida.worktool.utils.AccessibilityUtil
|
||||
import org.yameida.worktool.utils.Views
|
||||
import org.yameida.worktool.utils.WeworkRoomUtil
|
||||
|
||||
/**
|
||||
* 获取数据类型 500 实现类
|
||||
*/
|
||||
object WeworkGetImpl {
|
||||
|
||||
/**
|
||||
* 获取群信息
|
||||
* @param selectList 群名列表 为空时去群管理页查询并返回群聊页
|
||||
*/
|
||||
fun getGroupInfo(selectList: List<String>): Boolean {
|
||||
if (selectList.isNullOrEmpty()) {
|
||||
WeworkRoomUtil.intoGroupManager()
|
||||
val groupInfo = getGroupInfoDetail()
|
||||
WeworkController.weworkService.webSocketManager.send(groupInfo)
|
||||
backPress()
|
||||
} else {
|
||||
for (groupName in selectList) {
|
||||
if (WeworkRoomUtil.intoRoom(groupName) && WeworkRoomUtil.intoGroupManager()) {
|
||||
val groupInfo = getGroupInfoDetail()
|
||||
WeworkController.weworkService.webSocketManager.send(groupInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友信息
|
||||
* @param selectList 好友名列表
|
||||
*/
|
||||
fun getFriendInfo(selectList: List<String>): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取我的信息
|
||||
*/
|
||||
fun getMyInfo(): Boolean {
|
||||
if (!goHomeTab("我")) {
|
||||
LogUtils.d("未找到我的信息")
|
||||
goHomeTab("消息")
|
||||
val firstTv = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView)
|
||||
.firstOrNull { it.text == null }
|
||||
AccessibilityUtil.performClick(firstTv)
|
||||
sleep(1000)
|
||||
val newFirstTv = AccessibilityUtil.findOneByClazz(getRoot(), Views.TextView)
|
||||
val nickname = newFirstTv?.text?.toString()
|
||||
AccessibilityUtil.performClick(firstTv)
|
||||
if (nickname != null) {
|
||||
LogUtils.d("我的昵称: $nickname")
|
||||
val weworkMessageBean = WeworkMessageBean()
|
||||
weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO
|
||||
weworkMessageBean.myInfo = WeworkMessageBean.MyInfo().apply { name = nickname }
|
||||
WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.d("未找到我的昵称")
|
||||
return false
|
||||
}
|
||||
}
|
||||
AccessibilityUtil.performClick(AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView))
|
||||
sleep(1500)
|
||||
val relativeLayoutList = AccessibilityUtil.findAllByClazz(getRoot(), Views.RelativeLayout)
|
||||
val myInfo = WeworkMessageBean.MyInfo()
|
||||
for (relativeLayout in relativeLayoutList.filter { it.childCount >= 2 }) {
|
||||
val textViewList = AccessibilityUtil.findAllByClazz(relativeLayout, Views.TextView)
|
||||
if (textViewList.size >= 2) {
|
||||
val firstText = textViewList[0].text?.toString()
|
||||
if (firstText == "姓名" && myInfo.name == null) {
|
||||
myInfo.name = textViewList[1].text?.toString() ?: ""
|
||||
}
|
||||
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() ?: ""
|
||||
}
|
||||
if (firstText == "手机" && myInfo.phone == null) {
|
||||
myInfo.phone = textViewList[1].text?.toString() ?: ""
|
||||
}
|
||||
if (firstText == "职务" && myInfo.job == null) {
|
||||
myInfo.job = textViewList[1].text?.toString() ?: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
LogUtils.d("我的信息", myInfo)
|
||||
val weworkMessageBean = WeworkMessageBean()
|
||||
weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO
|
||||
weworkMessageBean.myInfo = myInfo
|
||||
WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群名、群主、群成员数、群公告
|
||||
*/
|
||||
fun getGroupInfoDetail(): WeworkMessageBean {
|
||||
val weworkMessageBean = WeworkMessageBean()
|
||||
weworkMessageBean.type = WeworkMessageBean.GET_GROUP_INFO
|
||||
val tvManagerFlag =
|
||||
AccessibilityUtil.findOneByText(getRoot(), "由企业微信用户创建,可邀请微信用户进群", "该群由企业微信用户创建")
|
||||
val button = AccessibilityUtil.findFrontNode(tvManagerFlag)
|
||||
val tvGroupName = AccessibilityUtil.findOneByClazz(button, Views.TextView)
|
||||
if (tvGroupName != null && tvGroupName.text != null) {
|
||||
LogUtils.d("群名: " + tvGroupName.text)
|
||||
weworkMessageBean.groupName = tvGroupName.text.toString()
|
||||
}
|
||||
val gridView = AccessibilityUtil.findOneByClazz(getRoot(), Views.GridView)
|
||||
if (gridView != null && gridView.childCount >= 2) {
|
||||
val tvOwnerName = AccessibilityUtil.findOneByClazz(gridView.getChild(0), Views.TextView)
|
||||
if (tvOwnerName != null && tvOwnerName.text != null) {
|
||||
LogUtils.d("群主: " + tvOwnerName.text)
|
||||
weworkMessageBean.groupOwner = tvOwnerName.text.toString()
|
||||
}
|
||||
}
|
||||
val tvCountFlag = AccessibilityUtil.findOneByText(getRoot(), "查看全部群成员")
|
||||
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 tvAnnouncementFlag = AccessibilityUtil.findOneByText(getRoot(), "群公告")
|
||||
val tvAnnouncement = AccessibilityUtil.findBackNode(tvAnnouncementFlag)
|
||||
if (tvAnnouncement != null && tvAnnouncement.text != null) {
|
||||
LogUtils.d("群公告: " + tvAnnouncement.text)
|
||||
weworkMessageBean.groupAnnouncement = tvAnnouncement.text.toString()
|
||||
}
|
||||
backPress()
|
||||
return weworkMessageBean
|
||||
}
|
||||
}
|
||||
348
app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt
Normal file
@@ -0,0 +1,348 @@
|
||||
package org.yameida.worktool.service
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import org.yameida.worktool.Demo
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
import org.yameida.worktool.service.WeworkController.mainLoopRunning
|
||||
import org.yameida.worktool.utils.*
|
||||
import java.lang.Exception
|
||||
import java.lang.StringBuilder
|
||||
|
||||
/**
|
||||
* 获取数据类型 201 202 主循环
|
||||
*/
|
||||
object WeworkLoopImpl {
|
||||
|
||||
var logIndex = 0
|
||||
|
||||
// @Synchronized
|
||||
// fun mainLoop() {
|
||||
// mainLoopRunning = true
|
||||
// if (!threadStart) {
|
||||
// threadStart = true
|
||||
// thread {
|
||||
// while (true) {
|
||||
// try {
|
||||
// if (mainLoopRunning) {
|
||||
// goHomeTab("消息")
|
||||
// //todo 下上滚动
|
||||
// if (mainLoopRunning && getChatroomList()) {
|
||||
// if (mainLoopRunning)
|
||||
// getChatMessageList()
|
||||
// mainLoopRunning = false
|
||||
// }
|
||||
// if (mainLoopRunning) {
|
||||
// getFriendRequest()
|
||||
// }
|
||||
// }
|
||||
// sleep(1000)
|
||||
// } catch (e: Exception) {
|
||||
// LogUtils.e(e)
|
||||
// error(e.printStackTrace())
|
||||
// mainLoopRunning = false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fun mainLoop() {
|
||||
mainLoopRunning = true
|
||||
try {
|
||||
while (mainLoopRunning) {
|
||||
goHomeTab("消息")
|
||||
//todo 下上滚动
|
||||
if (getChatroomList() && getChatMessageList()) {
|
||||
mainLoopRunning = false
|
||||
break
|
||||
}
|
||||
if (getFriendRequest()) {
|
||||
mainLoopRunning = false
|
||||
break
|
||||
}
|
||||
sleep(1000)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
mainLoopRunning = false
|
||||
error("ERROR mainLoop: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取通讯录好友请求
|
||||
*/
|
||||
fun getFriendRequest(): Boolean {
|
||||
val list = getRoot().findAccessibilityNodeInfosByText("通讯录")
|
||||
for (item in list) {
|
||||
if (item.parent.parent.parent.childCount == 5) {
|
||||
if (item.parent.childCount > 1) {
|
||||
LogUtils.d("通讯录有红点")
|
||||
AccessibilityUtil.performClick(item)
|
||||
sleep(500)
|
||||
val addButton = AccessibilityUtil.findOneByText(getRoot(), "添加客户")
|
||||
val backNode = AccessibilityUtil.findBackNode(addButton)
|
||||
LogUtils.d(backNode?.className)
|
||||
if (backNode?.className == Views.TextView) {
|
||||
LogUtils.d("有待添加客户")
|
||||
AccessibilityUtil.performClick(backNode)
|
||||
sleep(2000)
|
||||
AccessibilityUtil.findTextAndClick(getRoot(), "新的客户")
|
||||
sleep(500)
|
||||
var retry = 5
|
||||
while (retry-- > 0) {
|
||||
if (!AccessibilityUtil.findTextAndClick(getRoot(), "查看"))
|
||||
break
|
||||
sleep(2000)
|
||||
val nameList = passFriendRequest()
|
||||
if (nameList.isEmpty())
|
||||
break
|
||||
//TODO nameList 通过的好友加入演示脚本
|
||||
Demo.test2(nameList[0])
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
LogUtils.d("未发现待添加客户")
|
||||
}
|
||||
} else {
|
||||
LogUtils.v("通讯录无红点")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看好友请求并通过
|
||||
*/
|
||||
private fun passFriendRequest(): List<String> {
|
||||
val nameList = arrayListOf<String>()
|
||||
val imageView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ImageView)
|
||||
if (imageView != null) {
|
||||
val textViewList = AccessibilityUtil.findAllByClazz(imageView.parent, Views.TextView)
|
||||
val filter = textViewList.filter { it.text != null && it.text.toString() != "微信" }
|
||||
if (filter.isNotEmpty()) {
|
||||
val tvNick = filter[0]
|
||||
LogUtils.d("好友请求: " + tvNick.text)
|
||||
AccessibilityUtil.findTextAndClick(getRoot(), "通过验证")
|
||||
sleep(1000)
|
||||
AccessibilityUtil.findTextAndClick(getRoot(), "完成")
|
||||
sleep(5000)
|
||||
if (AccessibilityUtil.findTextAndClick(getRoot(), "确定")) {
|
||||
sleep(500)
|
||||
LogUtils.d("添加好友失败")
|
||||
} else {
|
||||
val weworkMessageBean = WeworkMessageBean()
|
||||
weworkMessageBean.type = WeworkMessageBean.GET_FRIEND_INFO
|
||||
weworkMessageBean.nameList = arrayListOf(tvNick.text.toString())
|
||||
WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
|
||||
nameList.add(tvNick.text.toString())
|
||||
}
|
||||
//回到上一页
|
||||
var retry = 5
|
||||
while (retry-- > 0 && !isAtHome()) {
|
||||
val textView = AccessibilityUtil.findOneByText(getRoot(), "新的客户")
|
||||
if (textView == null) {
|
||||
backPress()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nameList
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取聊天列表
|
||||
*/
|
||||
private fun getChatroomList(): Boolean {
|
||||
if (logIndex++ % 15 == 0) {
|
||||
LogUtils.i("读取首页聊天列表")
|
||||
log("读取首页聊天列表")
|
||||
}
|
||||
val listview = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
|
||||
if (listview != null) {
|
||||
if (listview.childCount >= 2) {
|
||||
if (checkUnreadChatRoom(listview)) {
|
||||
//进入聊天页
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("读取聊天列表失败")
|
||||
error("读取聊天列表失败")
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("读取聊天列表失败")
|
||||
error("读取聊天列表失败")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查首页-聊天列表是否有未读红点并点击进入
|
||||
* 获取红点
|
||||
*/
|
||||
private fun checkUnreadChatRoom(list: AccessibilityNodeInfo): Boolean {
|
||||
val spotNodeList = arrayListOf<AccessibilityNodeInfo>()
|
||||
for (i in 0 until list.childCount) {
|
||||
val item = list.getChild(i)
|
||||
if (item != null && Views.RelativeLayout.equals(item.className)) {
|
||||
if (item.childCount >= 2) {
|
||||
val spotNode = item.getChild(1)
|
||||
if (spotNode != null
|
||||
&& Views.TextView.equals(spotNode.className)
|
||||
&& spotNode.text != null
|
||||
&& spotNode.text.toString().replace("+", "").isDigitsOnly()
|
||||
) {
|
||||
spotNodeList.add(spotNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (spotNodeList.size > 0) {
|
||||
LogUtils.i("发现未读消息: " + spotNodeList.size + "条")
|
||||
log("发现未读消息: " + spotNodeList.size + "条")
|
||||
for (spotNode in spotNodeList) {
|
||||
if (AccessibilityUtil.performClick(spotNode)) {
|
||||
//进入聊天页 下一步 getChatMessageList
|
||||
break
|
||||
}
|
||||
}
|
||||
sleep(1000)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天页
|
||||
* 1.获取群名
|
||||
* 2.获取消息列表
|
||||
*/
|
||||
private fun getChatMessageList(): Boolean {
|
||||
AccessibilityUtil.performScrollDown(getRoot(), 0)
|
||||
val roomType = WeworkRoomUtil.getRoomType(getRoot())
|
||||
var titleList = WeworkRoomUtil.getRoomTitle(getRoot())
|
||||
if (titleList.contains("对方正在输入…")) {
|
||||
titleList = WeworkRoomUtil.getFriendName()
|
||||
}
|
||||
if (titleList.size > 0) {
|
||||
val title = titleList.joinToString()
|
||||
LogUtils.i("聊天: $title")
|
||||
log("聊天: $title")
|
||||
val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
|
||||
if (list != null) {
|
||||
LogUtils.d("消息条数: " + list.childCount)
|
||||
val messageList = arrayListOf<WeworkMessageBean.SubMessageBean>()
|
||||
for (i in 0 until list.childCount) {
|
||||
val item = list.getChild(i)
|
||||
if (item != null && item.childCount > 0) {
|
||||
messageList.add(parseChatMessageItem(item, roomType))
|
||||
}
|
||||
}
|
||||
WeworkController.weworkService.webSocketManager.send(
|
||||
WeworkMessageBean(
|
||||
null, null,
|
||||
WeworkMessageBean.TYPE_RECEIVE_MESSAGE_LIST,
|
||||
roomType,
|
||||
titleList,
|
||||
messageList,
|
||||
null
|
||||
)
|
||||
)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("未找到聊天消息列表")
|
||||
error("未找到聊天消息列表")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析消息列表里的一条消息
|
||||
*/
|
||||
private fun parseChatMessageItem(
|
||||
node: AccessibilityNodeInfo,
|
||||
roomType: Int
|
||||
): WeworkMessageBean.SubMessageBean {
|
||||
val message: WeworkMessageBean.SubMessageBean
|
||||
val nameList = arrayListOf<String>()
|
||||
val itemMessageList = arrayListOf<WeworkMessageBean.ItemMessageBean>()
|
||||
LogUtils.d("开始解析一条消息...")
|
||||
//消息头(在消息主体上方 如时间信息)
|
||||
val linearLayoutItem = AccessibilityUtil.findOneByClazz(node, Views.LinearLayout, 1)
|
||||
if (linearLayoutItem != null) {
|
||||
val sb = StringBuilder("消息头: ")
|
||||
val tvList = AccessibilityUtil.findAllByClazz(linearLayoutItem, Views.TextView)
|
||||
for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) {
|
||||
val text = item.text.toString()
|
||||
val itemMessage = WeworkMessageBean.ItemMessageBean(0, text)
|
||||
sb.append(text).append("\t")
|
||||
itemMessageList.add(itemMessage)
|
||||
}
|
||||
LogUtils.d(sb.toString())
|
||||
}
|
||||
//消息主体
|
||||
val relativeLayoutItem = AccessibilityUtil.findOneByClazz(node, Views.RelativeLayout, 1)
|
||||
if (relativeLayoutItem != null && relativeLayoutItem.childCount >= 2) {
|
||||
if (Views.ImageView.equals(relativeLayoutItem.getChild(0).className)) {
|
||||
LogUtils.v("头像在左边 本条消息发送者为其他联系人")
|
||||
nameList.addAll(WeworkTextUtil.getNameList(node))
|
||||
var textType = WeworkMessageBean.TEXT_TYPE_UNKNOWN
|
||||
val relativeLayoutContent =
|
||||
AccessibilityUtil.findOneByClazz(relativeLayoutItem, Views.RelativeLayout, 2)
|
||||
if (relativeLayoutContent != null) {
|
||||
// AccessibilityUtil.printNodeClazzTree(relativeLayoutContent)
|
||||
textType = WeworkTextUtil.getTextType(relativeLayoutContent)
|
||||
LogUtils.v("textType: $textType")
|
||||
val tvList =
|
||||
AccessibilityUtil.findAllByClazz(relativeLayoutContent, Views.TextView)
|
||||
for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) {
|
||||
val text = item.text.toString()
|
||||
LogUtils.d(text)
|
||||
val itemMessage = WeworkMessageBean.ItemMessageBean(2, text)
|
||||
itemMessageList.add(itemMessage)
|
||||
}
|
||||
}
|
||||
message = WeworkMessageBean.SubMessageBean(0, textType, itemMessageList, nameList)
|
||||
} else if (Views.ImageView.equals(relativeLayoutItem.getChild(1).className)) {
|
||||
LogUtils.v("头像在右边 本条消息发送者为自己")
|
||||
val tvList = AccessibilityUtil.findAllByClazz(relativeLayoutItem, Views.TextView)
|
||||
for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) {
|
||||
val text = item.text.toString()
|
||||
LogUtils.d(text)
|
||||
val itemMessage = WeworkMessageBean.ItemMessageBean(2, text)
|
||||
itemMessageList.add(itemMessage)
|
||||
}
|
||||
message = WeworkMessageBean.SubMessageBean(1, 0, itemMessageList, nameList)
|
||||
} else {
|
||||
// 没有头像的消息(撤销消息、其他可能的系统消息)
|
||||
val tvList = AccessibilityUtil.findAllByClazz(node, Views.TextView)
|
||||
for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) {
|
||||
val text = item.text.toString()
|
||||
LogUtils.d(text)
|
||||
val itemMessage = WeworkMessageBean.ItemMessageBean(1, text)
|
||||
itemMessageList.add(itemMessage)
|
||||
}
|
||||
message = WeworkMessageBean.SubMessageBean(2, 0, itemMessageList, nameList)
|
||||
LogUtils.e("消息解析异常 未知异常")
|
||||
}
|
||||
} else {
|
||||
// 没有头像的消息(撤销消息、其他可能的系统消息)
|
||||
val sb = StringBuilder("未发现头像 本条消息发送者未知")
|
||||
val tvList = AccessibilityUtil.findAllByClazz(node, Views.TextView)
|
||||
for (item in tvList.filter { it.text != null && !it.text.isNullOrBlank() }) {
|
||||
val text = item.text.toString()
|
||||
sb.append(text).append("/t")
|
||||
val itemMessage = WeworkMessageBean.ItemMessageBean(0, text)
|
||||
itemMessageList.add(itemMessage)
|
||||
}
|
||||
LogUtils.d(sb.toString())
|
||||
message = WeworkMessageBean.SubMessageBean(2, 0, itemMessageList, nameList)
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,707 @@
|
||||
package org.yameida.worktool.service
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
import org.yameida.worktool.utils.AccessibilityUtil
|
||||
import org.yameida.worktool.utils.Views
|
||||
import org.yameida.worktool.utils.WeworkRoomUtil
|
||||
import org.yameida.worktool.utils.WeworkTextUtil
|
||||
|
||||
/**
|
||||
* 全局操作类型 200 实现类
|
||||
*/
|
||||
object WeworkOperationImpl {
|
||||
|
||||
/**
|
||||
* 在房间内发送消息
|
||||
* @param titleList 房间名称
|
||||
* @param receivedContent 回复内容
|
||||
* @see WeworkMessageBean.TEXT_TYPE
|
||||
*/
|
||||
fun sendMessage(titleList: List<String>, receivedContent: String): Boolean {
|
||||
for (title in titleList) {
|
||||
if (WeworkRoomUtil.intoRoom(title)) {
|
||||
sendChatMessage(receivedContent)
|
||||
LogUtils.d("$title: 发送成功")
|
||||
} else {
|
||||
LogUtils.d("$title: 发送失败")
|
||||
error("$title: 发送失败 $receivedContent")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 在房间内指定回复消息
|
||||
* @param titleList 房间名称
|
||||
* @param receivedName 原始消息的发送者姓名
|
||||
* @param originalContent 原始消息的内容
|
||||
* @param textType 原始消息的消息类型
|
||||
* @param receivedContent 回复内容
|
||||
* @see WeworkMessageBean.TEXT_TYPE
|
||||
*/
|
||||
fun replyMessage(
|
||||
titleList: List<String>,
|
||||
receivedName: String,
|
||||
originalContent: String,
|
||||
textType: Int,
|
||||
receivedContent: String
|
||||
): Boolean {
|
||||
for (title in titleList) {
|
||||
if (WeworkRoomUtil.intoRoom(title)) {
|
||||
if (WeworkTextUtil.longClickMessageItem(
|
||||
AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView),
|
||||
textType,
|
||||
receivedName,
|
||||
originalContent,
|
||||
"回复"
|
||||
)
|
||||
) {
|
||||
LogUtils.d("开始回复")
|
||||
sleep(1000)
|
||||
sendChatMessage(receivedContent, "[自动回复]")
|
||||
LogUtils.d("$title: 回复成功")
|
||||
return true
|
||||
} else {
|
||||
LogUtils.d("$title: 回复失败")
|
||||
error("$title: 回复失败 $receivedContent")
|
||||
}
|
||||
} else {
|
||||
LogUtils.d("$title: 回复失败")
|
||||
error("$title: 回复失败 $receivedContent")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 在房间内转发消息
|
||||
* @param titleList 房间名称
|
||||
* @param receivedName 原始消息的发送者姓名
|
||||
* @param originalContent 原始消息的内容
|
||||
* @param textType 原始消息的消息类型
|
||||
* @param nameList 待转发姓名列表
|
||||
* @param extraText 附加留言 选填
|
||||
* @see WeworkMessageBean.TEXT_TYPE
|
||||
*/
|
||||
fun relayMessage(
|
||||
titleList: List<String>,
|
||||
receivedName: String,
|
||||
originalContent: String,
|
||||
textType: Int,
|
||||
nameList: List<String>,
|
||||
extraText: String? = null
|
||||
): Boolean {
|
||||
for (title in titleList) {
|
||||
if (WeworkRoomUtil.intoRoom(title)) {
|
||||
if (WeworkTextUtil.longClickMessageItem(
|
||||
AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView),
|
||||
textType,
|
||||
receivedName,
|
||||
originalContent,
|
||||
"转发"
|
||||
)
|
||||
) {
|
||||
LogUtils.d("开始转发")
|
||||
sleep(1000)
|
||||
relaySelectTarget(nameList, extraText)
|
||||
}
|
||||
LogUtils.d("$title: 转发成功")
|
||||
} else {
|
||||
LogUtils.d("$title: 转发失败")
|
||||
error("$title: 转发失败 $originalContent")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化群设置
|
||||
* 1.修改群名
|
||||
* 2.添加群成员 默认为空
|
||||
* 3.修改群公告 默认为空
|
||||
* 4.成员改群名 默认禁止
|
||||
* 5.私自邀请 默认开启
|
||||
* 6.设置群管理员 延迟开发
|
||||
* 7.设置入群欢迎语 默认为空
|
||||
* 8.拉入机器人 暂不开发
|
||||
* 9.防骚扰 默认警告模式
|
||||
* 10.使用群配置模板 延迟开发
|
||||
* 11.消息免打扰 默认禁止
|
||||
* 12.保存到通讯录 默认开启
|
||||
* 注:群配置模板 1.群名称 2.禁群改名(使用) 3.设置管理员 4.入群欢迎语(使用) 5.自动回复 6.防骚扰规则(使用)
|
||||
* 必须人工给机器人预先设置防骚扰规则 规则名"机器人"
|
||||
* 必须人工给机器人预先设置群配置模板 模板名"机器人"
|
||||
* @param groupName 修改群名称
|
||||
* @param selectList 添加群成员名称列表 选填
|
||||
* @param groupAnnouncement 修改群公告 选填
|
||||
*/
|
||||
fun initGroup(
|
||||
groupName: String,
|
||||
selectList: List<String>?,
|
||||
groupAnnouncement: String?
|
||||
): Boolean {
|
||||
if (!createGroup() || !groupRename(groupName) || !groupAddMember(selectList)
|
||||
|| !groupChangeAnnouncement(groupAnnouncement)
|
||||
) return false
|
||||
backPress()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入群聊并修改群配置
|
||||
* 群名称、群公告、拉人、踢人
|
||||
* @param groupName 待修改的群
|
||||
* @param newGroupName 修改群名 选填
|
||||
* @param newGroupAnnouncement 修改群公告 选填
|
||||
* @param selectList 添加群成员名称列表/拉人 选填
|
||||
* @param showMessageHistory 拉人是否附带历史记录 选填
|
||||
* @param removeList 移除群成员名称列表/踢人 选填
|
||||
*/
|
||||
fun intoGroupAndConfig(
|
||||
groupName: String,
|
||||
newGroupName: String?,
|
||||
newGroupAnnouncement: String?,
|
||||
selectList: List<String>?,
|
||||
showMessageHistory: Boolean = false,
|
||||
removeList: List<String>?
|
||||
): Boolean {
|
||||
if (WeworkRoomUtil.intoRoom(groupName)) {
|
||||
if (newGroupName != null) {
|
||||
groupRename(newGroupName)
|
||||
}
|
||||
if (selectList != null) {
|
||||
groupAddMember(selectList, showMessageHistory)
|
||||
}
|
||||
if (removeList != null) {
|
||||
groupRemoveMember(removeList)
|
||||
}
|
||||
if (newGroupAnnouncement != null) {
|
||||
groupChangeAnnouncement(newGroupAnnouncement)
|
||||
}
|
||||
backPress()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送微盘图片
|
||||
* @param titleList 待发送姓名列表
|
||||
* @param objectName 图片名称
|
||||
* @param extraText 附加留言 可选
|
||||
*/
|
||||
fun pushMicroDiskImage(
|
||||
titleList: List<String>,
|
||||
objectName: String,
|
||||
extraText: String? = null
|
||||
): Boolean {
|
||||
goHomeTab("工作台")
|
||||
val node = AccessibilityUtil.scrollAndFindByText(getRoot(), "微盘")
|
||||
if (node != null) {
|
||||
AccessibilityUtil.performClick(node)
|
||||
sleep(2000)
|
||||
val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button)
|
||||
if (buttonList.size >= 4) {
|
||||
AccessibilityUtil.performClick(buttonList[2])
|
||||
sleep(1000)
|
||||
AccessibilityUtil.findTextInput(getRoot(), objectName)
|
||||
sleep(2000)
|
||||
val editText = AccessibilityUtil.findOneByClazz(getRoot(), Views.EditText)
|
||||
val backNode = AccessibilityUtil.findBackNode(editText)
|
||||
val imageViewList = AccessibilityUtil.findAllByClazz(backNode, Views.ImageView)
|
||||
if (imageViewList.size >= 2) {
|
||||
AccessibilityUtil.performClick(imageViewList[1])
|
||||
sleep(2000)
|
||||
val shareFileButton = AccessibilityUtil.findOneByDesc(getRoot(), "以原文件分享")
|
||||
AccessibilityUtil.performClick(shareFileButton)
|
||||
sleep(2000)
|
||||
val shareToWorkButton = AccessibilityUtil.findOneByText(getRoot(true), "发送给同事")
|
||||
AccessibilityUtil.performClick(shareToWorkButton)
|
||||
sleep(2000)
|
||||
relaySelectTarget(titleList, extraText)
|
||||
sleep(2000)
|
||||
val stayButton = AccessibilityUtil.findOneByText(getRoot(), "留在企业微信")
|
||||
AccessibilityUtil.performClick(stayButton)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("微盘未搜索到相关文件: $objectName")
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到微盘内搜索")
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到微盘")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送微盘文件
|
||||
* @param titleList 待发送姓名列表
|
||||
* @param objectName 文件名称
|
||||
* @param extraText 附加留言 可选
|
||||
*/
|
||||
fun pushMicroDiskFile(
|
||||
titleList: List<String>,
|
||||
objectName: String,
|
||||
extraText: String? = null
|
||||
): Boolean {
|
||||
goHomeTab("工作台")
|
||||
val node = AccessibilityUtil.scrollAndFindByText(getRoot(), "微盘")
|
||||
if (node != null) {
|
||||
AccessibilityUtil.performClick(node)
|
||||
sleep(2000)
|
||||
val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button)
|
||||
if (buttonList.size >= 4) {
|
||||
AccessibilityUtil.performClick(buttonList[2])
|
||||
sleep(1000)
|
||||
AccessibilityUtil.findTextInput(getRoot(), objectName)
|
||||
sleep(2000)
|
||||
val editText = AccessibilityUtil.findOneByClazz(getRoot(), Views.EditText)
|
||||
val backNode = AccessibilityUtil.findBackNode(editText)
|
||||
val imageViewList = AccessibilityUtil.findAllByClazz(backNode, Views.ImageView)
|
||||
if (imageViewList.size >= 2) {
|
||||
AccessibilityUtil.performClick(imageViewList[1])
|
||||
sleep(2000)
|
||||
val shareFileButton = AccessibilityUtil.findOneByDesc(getRoot(), "转发")
|
||||
AccessibilityUtil.performClick(shareFileButton)
|
||||
sleep(2000)
|
||||
relaySelectTarget(titleList, extraText)
|
||||
sleep(2000)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("微盘未搜索到相关文件: $objectName")
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到微盘内搜索")
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到微盘")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送任意小程序
|
||||
* @param titleList 待发送姓名列表
|
||||
* @param objectName 小程序名称
|
||||
* @param extraText 附加留言 可选
|
||||
*/
|
||||
fun pushMicroprogram(
|
||||
titleList: List<String>,
|
||||
objectName: String,
|
||||
extraText: String? = null
|
||||
): Boolean {
|
||||
goHomeTab("工作台")
|
||||
val node = AccessibilityUtil.scrollAndFindByText(getRoot(), "用过的小程序")
|
||||
if (node != null) {
|
||||
AccessibilityUtil.performClick(node)
|
||||
sleep(2000)
|
||||
val textViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView)
|
||||
if (textViewList.size > 3) {
|
||||
AccessibilityUtil.performClick(textViewList[2])
|
||||
sleep(1000)
|
||||
AccessibilityUtil.findTextInput(getRoot(), objectName)
|
||||
sleep(2000)
|
||||
AccessibilityUtil.findListOneAndClick(getRoot(), 1)
|
||||
sleep(2000)
|
||||
//todo 转发小程序
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("未找到小程序内搜索")
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到小程序")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送腾讯文档
|
||||
* @param titleList 待发送姓名列表
|
||||
* @param objectName 小程序名称
|
||||
* @param extraText 附加留言 可选
|
||||
*/
|
||||
fun pushOffice(
|
||||
titleList: List<String>,
|
||||
objectName: String,
|
||||
extraText: String? = null
|
||||
): Boolean {
|
||||
goHomeTab("文档")
|
||||
val allButton = AccessibilityUtil.findOneByText(getRoot(), "全部")
|
||||
if (allButton == null) {
|
||||
LogUtils.e("未找到全部按钮")
|
||||
return false
|
||||
}
|
||||
AccessibilityUtil.performClick(allButton)
|
||||
sleep(1000)
|
||||
val myFileButton = AccessibilityUtil.findOneByText(getRoot(), "共享空间")
|
||||
if (myFileButton == null) {
|
||||
LogUtils.e("未找到共享空间按钮")
|
||||
return false
|
||||
}
|
||||
AccessibilityUtil.performClick(myFileButton)
|
||||
sleep(2000)
|
||||
val buttonList = AccessibilityUtil.findAllByClazz(getRoot(), Views.Button)
|
||||
if (buttonList.size >= 4) {
|
||||
AccessibilityUtil.performClick(buttonList[3])
|
||||
sleep(1000)
|
||||
AccessibilityUtil.findTextInput(getRoot(), objectName)
|
||||
sleep(2000)
|
||||
val editText = AccessibilityUtil.findOneByClazz(getRoot(), Views.EditText)
|
||||
val backNode = AccessibilityUtil.findBackNode(editText)
|
||||
val imageViewList = AccessibilityUtil.findAllByClazz(backNode, Views.ImageView)
|
||||
if (imageViewList.size >= 2) {
|
||||
AccessibilityUtil.performClick(imageViewList[1])
|
||||
sleep(2000)
|
||||
val shareFileButton = AccessibilityUtil.findOneByDesc(getRoot(), "转发")
|
||||
AccessibilityUtil.performClick(shareFileButton)
|
||||
sleep(2000)
|
||||
relaySelectTarget(titleList, extraText)
|
||||
sleep(2000)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("文档未搜索到相关文件: $objectName")
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到文档搜索按钮")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示群信息
|
||||
* @see WeworkMessageBean.SHOW_GROUP_INFO
|
||||
* @param titleList 待查询群名
|
||||
* @param receivedName 原始消息的发送者姓名
|
||||
* @param originalContent 原始消息的内容
|
||||
* @param textType 原始消息的消息类型
|
||||
*/
|
||||
fun showGroupInfo(
|
||||
titleList: MutableList<String>,
|
||||
receivedName: String,
|
||||
originalContent: String,
|
||||
textType: Int
|
||||
): Boolean {
|
||||
for (groupName in titleList) {
|
||||
if (WeworkRoomUtil.intoRoom(groupName) && WeworkRoomUtil.intoGroupManager()) {
|
||||
val groupInfo = WeworkGetImpl.getGroupInfoDetail()
|
||||
groupInfo.titleList = arrayListOf(groupName)
|
||||
groupInfo.type = WeworkMessageBean.SHOW_GROUP_INFO
|
||||
groupInfo.receivedName = receivedName
|
||||
groupInfo.originalContent = originalContent
|
||||
groupInfo.textType = textType
|
||||
WeworkController.weworkService.webSocketManager.send(groupInfo)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 转发消息到目标列表
|
||||
* 支持场景:长按消息转发、微盘图片转发
|
||||
* selectList 昵称或群名列表
|
||||
* extraText 转发是否附加一条文本
|
||||
*/
|
||||
private fun relaySelectTarget(selectList: List<String>, extraText: String? = null): Boolean {
|
||||
val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
|
||||
if (list != null) {
|
||||
val frontNode = AccessibilityUtil.findFrontNode(list)
|
||||
val textViewList = AccessibilityUtil.findAllByClazz(frontNode, Views.TextView)
|
||||
if (textViewList.size >= 2) {
|
||||
val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2]
|
||||
val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1]
|
||||
AccessibilityUtil.performClick(multiButton)
|
||||
sleep(1000)
|
||||
AccessibilityUtil.performClick(searchButton)
|
||||
sleep(1000)
|
||||
for (select in selectList) {
|
||||
AccessibilityUtil.findTextInput(getRoot(), select)
|
||||
sleep(2000)
|
||||
val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
|
||||
val imageView =
|
||||
AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView)
|
||||
if (imageView != null) {
|
||||
AccessibilityUtil.performClick(imageView)
|
||||
}
|
||||
sleep(1000)
|
||||
}
|
||||
val confirmButton =
|
||||
AccessibilityUtil.findOneByText(getRoot(), "确定(${selectList.size})")
|
||||
if (confirmButton != null) {
|
||||
AccessibilityUtil.performClick(confirmButton)
|
||||
sleep(1000)
|
||||
if (!extraText.isNullOrBlank()) {
|
||||
LogUtils.d("extraText: $extraText")
|
||||
AccessibilityUtil.findTextInput(getRoot(), extraText)
|
||||
sleep(1000)
|
||||
}
|
||||
val sendButtonList = getRoot().findAccessibilityNodeInfosByText("发送")
|
||||
for (sendButton in sendButtonList.filter { it.text != null }) {
|
||||
if (sendButton.text == "发送" || sendButton.text == "发送(${selectList.size})") {
|
||||
AccessibilityUtil.performClick(sendButton)
|
||||
return true
|
||||
}
|
||||
}
|
||||
LogUtils.e("未发现发送按钮: ")
|
||||
return false
|
||||
} else {
|
||||
LogUtils.e("未发现确认按钮: ")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未发现搜索和多选按钮: ")
|
||||
return false
|
||||
}
|
||||
}
|
||||
LogUtils.e("未知错误: ")
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个外部群
|
||||
*/
|
||||
private fun createGroup(): Boolean {
|
||||
goHomeTab("工作台")
|
||||
val textViewGroup = AccessibilityUtil.scrollAndFindByText(getRoot(), "客户群")
|
||||
if (AccessibilityUtil.performClick(textViewGroup)) {
|
||||
LogUtils.d("进入客户群应用")
|
||||
sleep(2000)
|
||||
val textView = AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群")
|
||||
AccessibilityUtil.performClick(textView)
|
||||
sleep(3000)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.d("未找到客户群应用")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改群名称
|
||||
*/
|
||||
private fun groupRename(groupName: String): Boolean {
|
||||
if (WeworkRoomUtil.intoGroupManager()) {
|
||||
val textView =
|
||||
AccessibilityUtil.findOneByText(getRoot(), "由企业微信用户创建,可邀请微信用户进群", "该群由企业微信用户创建")
|
||||
val button = AccessibilityUtil.findFrontNode(textView)
|
||||
if (button != null) {
|
||||
AccessibilityUtil.performClick(button)
|
||||
sleep(1000)
|
||||
AccessibilityUtil.findTextInput(getRoot(), groupName)
|
||||
val confirmButton = AccessibilityUtil.findOneByText(getRoot(), "确定")
|
||||
AccessibilityUtil.performClick(confirmButton)
|
||||
sleep(2000)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("未找到填写群名按钮")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加群成员/拉人
|
||||
* 默认不附带历史记录
|
||||
*/
|
||||
private fun groupAddMember(
|
||||
selectList: List<String>? = null,
|
||||
showMessageHistory: Boolean = false
|
||||
): Boolean {
|
||||
if (selectList.isNullOrEmpty()) return true
|
||||
if (WeworkRoomUtil.intoGroupManager()) {
|
||||
val gridView = AccessibilityUtil.findOneByClazz(getRoot(), Views.GridView)
|
||||
if (gridView != null && gridView.childCount >= 2) {
|
||||
if (gridView.childCount == 2) {
|
||||
AccessibilityUtil.performClick(gridView.getChild(gridView.childCount - 1))
|
||||
} else {
|
||||
AccessibilityUtil.performClick(gridView.getChild(gridView.childCount - 2))
|
||||
}
|
||||
sleep(1000)
|
||||
} else {
|
||||
LogUtils.e("未找到添加成员按钮")
|
||||
return false
|
||||
}
|
||||
val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
|
||||
if (list != null) {
|
||||
val frontNode = AccessibilityUtil.findFrontNode(list)
|
||||
val textViewList = AccessibilityUtil.findAllByClazz(frontNode, Views.TextView)
|
||||
if (textViewList.size >= 2) {
|
||||
val multiButton = textViewList.lastOrNull()
|
||||
AccessibilityUtil.performClick(multiButton)
|
||||
sleep(1000)
|
||||
for (select in selectList) {
|
||||
AccessibilityUtil.findTextInput(getRoot(), select)
|
||||
sleep(2000)
|
||||
val selectListView =
|
||||
AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
|
||||
val imageView =
|
||||
AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView)
|
||||
if (imageView != null) {
|
||||
AccessibilityUtil.performClick(imageView)
|
||||
}
|
||||
sleep(1000)
|
||||
}
|
||||
if (showMessageHistory) {
|
||||
val button = AccessibilityUtil.findOneByText(getRoot(), "附带聊天记录")
|
||||
if (button != null) AccessibilityUtil.performClick(button)
|
||||
}
|
||||
val confirmButton =
|
||||
AccessibilityUtil.findOneByText(getRoot(), "确定(${selectList.size})")
|
||||
if (confirmButton != null) {
|
||||
AccessibilityUtil.performClick(confirmButton)
|
||||
sleep(1000)
|
||||
} else {
|
||||
LogUtils.e("未发现确认按钮: ")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到搜索按钮")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到成员列表")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除群成员/踢人
|
||||
*/
|
||||
private fun groupRemoveMember(removeList: List<String>): Boolean {
|
||||
if (WeworkRoomUtil.intoGroupManager()) {
|
||||
val gridView = AccessibilityUtil.findOneByClazz(getRoot(), Views.GridView)
|
||||
if (gridView != null && gridView.childCount >= 2) {
|
||||
if (gridView.childCount == 2) {
|
||||
return true
|
||||
} else {
|
||||
AccessibilityUtil.performClick(gridView.getChild(gridView.childCount - 1))
|
||||
}
|
||||
sleep(1000)
|
||||
} else {
|
||||
LogUtils.e("未找到删除成员按钮")
|
||||
return false
|
||||
}
|
||||
val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
|
||||
if (list != null) {
|
||||
val frontNode = AccessibilityUtil.findFrontNode(list)
|
||||
val textViewList = AccessibilityUtil.findAllByClazz(frontNode, Views.TextView)
|
||||
if (textViewList.size >= 2) {
|
||||
val multiButton = textViewList.lastOrNull()
|
||||
AccessibilityUtil.performClick(multiButton)
|
||||
sleep(1000)
|
||||
for (select in removeList) {
|
||||
AccessibilityUtil.findTextInput(getRoot(), select)
|
||||
sleep(2000)
|
||||
val selectListView =
|
||||
AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView)
|
||||
val imageView =
|
||||
AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView)
|
||||
if (imageView != null) {
|
||||
AccessibilityUtil.performClick(imageView)
|
||||
}
|
||||
sleep(1000)
|
||||
}
|
||||
val confirmButton =
|
||||
AccessibilityUtil.findOneByText(getRoot(), "移出(${removeList.size})")
|
||||
if (confirmButton != null) {
|
||||
AccessibilityUtil.performClick(confirmButton)
|
||||
sleep(1000)
|
||||
} else {
|
||||
LogUtils.e("未发现移出按钮: ")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到搜索按钮")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到成员列表")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改群公告
|
||||
* 注:首次为发布 后续为编辑
|
||||
*/
|
||||
private fun groupChangeAnnouncement(groupAnnouncement: String? = null): Boolean {
|
||||
if (groupAnnouncement == null) return true
|
||||
if (WeworkRoomUtil.intoGroupManager()) {
|
||||
val textView = AccessibilityUtil.findOneByText(getRoot(), "群公告")
|
||||
if (textView != null) {
|
||||
AccessibilityUtil.performClick(textView)
|
||||
sleep(1000)
|
||||
val editButton = AccessibilityUtil.findOneByText(getRoot(), "编辑")
|
||||
if (editButton != null) {
|
||||
LogUtils.d("群公告编辑中: $groupAnnouncement")
|
||||
AccessibilityUtil.performClick(editButton)
|
||||
sleep(1000)
|
||||
}
|
||||
if (AccessibilityUtil.findTextInput(getRoot(), groupAnnouncement)) {
|
||||
LogUtils.d("群公告发布中: $groupAnnouncement")
|
||||
sleep(500)
|
||||
val button = AccessibilityUtil.findOneByText(getRoot(), "发布")
|
||||
AccessibilityUtil.performClick(button)
|
||||
sleep(1000)
|
||||
val publishButtonList = getRoot().findAccessibilityNodeInfosByText("发布")
|
||||
if (publishButtonList.size >= 2) {
|
||||
AccessibilityUtil.performClick(publishButtonList[1])
|
||||
}
|
||||
sleep(3000)
|
||||
} else {
|
||||
LogUtils.e("无法进行群公告发布和编辑: ")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
LogUtils.e("未找到群公告按钮")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
private fun sendChatMessage(text: String, prefix: String = "") {
|
||||
val editText = AccessibilityUtil.findOneByClazz(getRoot(), Views.EditText)
|
||||
if (editText != null) {
|
||||
AccessibilityUtil.editTextInput(editText, prefix + text)
|
||||
//输入完文字等待出现发送按钮
|
||||
sleep(500)
|
||||
} else {
|
||||
LogUtils.e("未找到输入框")
|
||||
error("未找到输入框")
|
||||
}
|
||||
var index = 0
|
||||
while (index++ < 5) {
|
||||
val buttonList = getRoot().findAccessibilityNodeInfosByText("发送")
|
||||
var sendButton: AccessibilityNodeInfo? = null
|
||||
for (button in buttonList) {
|
||||
if (button.className == Views.Button) {
|
||||
sendButton = button
|
||||
}
|
||||
}
|
||||
if (sendButton != null) {
|
||||
LogUtils.i("发送消息: \n$text")
|
||||
log("发送消息: \n$text")
|
||||
AccessibilityUtil.performClick(sendButton)
|
||||
sleep(500)
|
||||
break
|
||||
} else {
|
||||
LogUtils.e("未找到发送按钮")
|
||||
error("未找到发送按钮")
|
||||
}
|
||||
sleep(500)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
122
app/src/main/java/org/yameida/worktool/service/WeworkService.kt
Normal file
@@ -0,0 +1,122 @@
|
||||
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.Build
|
||||
import android.util.Log
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.blankj.utilcode.util.*
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
import org.yameida.worktool.Constant
|
||||
import org.yameida.worktool.Demo
|
||||
import org.yameida.worktool.config.WebConfig
|
||||
import org.yameida.worktool.utils.*
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* 企业微信辅助服务
|
||||
* rootInActiveWindow获取的是当前交互界面窗口的根view 需要验证包名
|
||||
* event.source则不需要验证包名获取窗口并可获得事件详情
|
||||
*/
|
||||
class WeworkService : AccessibilityService() {
|
||||
lateinit var webSocketManager: WebSocketManager
|
||||
|
||||
override fun onServiceConnected() {
|
||||
LogUtils.i("初始化成功")
|
||||
//隐藏软键盘模式
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
softKeyboardController.showMode = SHOW_MODE_HIDDEN
|
||||
}
|
||||
WeworkController.weworkService = this
|
||||
//初始化长连接
|
||||
initWebSocket()
|
||||
//初始化消息处理器
|
||||
MyLooper.init()
|
||||
//开发者可以在这里添加测试代码 启动时调用一次
|
||||
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(WebConfig.WEWORK_NOTIFY))
|
||||
}
|
||||
|
||||
private fun initWebSocket() {
|
||||
val url =
|
||||
WebConfig.WEWORK_URL + SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID)
|
||||
val listener = EchoWebSocketListener()
|
||||
LogUtils.d("initWebSocket: $url")
|
||||
webSocketManager = WebSocketManager(url, listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* TYPE_WINDOW_CONTENT_CHANGED 内容变化
|
||||
* TYPE_VIEW_SCROLLED 列表滚动
|
||||
* @param event
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
override fun onAccessibilityEvent(event: AccessibilityEvent) {
|
||||
}
|
||||
|
||||
override fun onInterrupt() {
|
||||
LogUtils.i("onInterrupt")
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
LogUtils.i("onDestroy")
|
||||
//隐藏软键盘模式
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
softKeyboardController.showMode = SHOW_MODE_AUTO
|
||||
}
|
||||
webSocketManager.close(1000, "service Destroy")
|
||||
}
|
||||
|
||||
class EchoWebSocketListener() : WebSocketListener() {
|
||||
private lateinit var socket: WebSocket
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
socket = webSocket
|
||||
Log.e("ChatMaskActivityListener", "链接建立")
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
LogUtils.i("onMessage: $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("ChatMaskActivityListener", "链接关闭 $reason")
|
||||
}
|
||||
|
||||
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
||||
super.onClosing(webSocket, code, reason)
|
||||
socket.close(code, reason)
|
||||
Log.e("ChatMaskActivityListener", "服务端关闭连接 $code: $reason")
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
//服务器中断
|
||||
|
||||
Log.e("ChatMaskActivityListener", "链接错误: " + t.toString() + response.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
package org.yameida.worktool.utils
|
||||
|
||||
import android.accessibilityservice.AccessibilityService
|
||||
import android.accessibilityservice.GestureDescription
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.graphics.Path
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
|
||||
/**
|
||||
* 1.查询类
|
||||
* findOneByClazz 按类名寻找节点和子节点内的一个匹配项
|
||||
* findAllByClazz 按类名寻找节点和子节点内的所有匹配项
|
||||
* findFrontNode 查找节点的前兄弟节点
|
||||
* findBackNode 查找节点的后兄弟节点
|
||||
* findCanScrollNode 返回可滚动元素集合
|
||||
* findOneByDesc 按描述寻找节点和子节点内的一个匹配项
|
||||
* findOneByText 按文本(关键词)寻找节点和子节点内的一个匹配项
|
||||
* findAllByText 按文本(关键词)寻找节点和子节点内的所有匹配项
|
||||
*
|
||||
* 2.全局操作
|
||||
* globalGoBack 回退
|
||||
* globalGoHome 回桌面
|
||||
*
|
||||
* 3.窗口操作
|
||||
* printNodeClazzTree 深度搜索打印节点及其子节点
|
||||
* performScrollUp 对某个节点向上滚动 未生效
|
||||
* performScrollDown 对某个节点向下滚动 未生效
|
||||
* performClick 对某个节点进行点击
|
||||
* performXYClick 输入x, y坐标模拟点击事件
|
||||
* editTextInput 编辑EditView(非粘贴 推荐)
|
||||
* findTextAndClick 寻找第一个文本匹配(关键词)并点击
|
||||
* findTextInput 寻找第一个EditView编辑框并输入文本
|
||||
* findListOneAndClick 寻找第一个列表并点击指定条目(默认点击第一个条目)
|
||||
* scrollAndFindByText 滚动并按文本寻找第一个控件
|
||||
* performClickWithSon 对某个节点或子节点进行点击
|
||||
* performLongClick 对某个节点或父节点进行长按
|
||||
* performLongClickWithSon 对某个节点或子节点进行长按
|
||||
*
|
||||
*/
|
||||
object AccessibilityUtil {
|
||||
private const val tag = "AccessibilityUtil"
|
||||
|
||||
//编辑EditView(非粘贴 推荐)
|
||||
fun editTextInput(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean {
|
||||
val nodeInfo: AccessibilityNodeInfo = nodeInfo ?: return false
|
||||
val arguments = Bundle()
|
||||
arguments.putCharSequence(
|
||||
AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
|
||||
text
|
||||
)
|
||||
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
|
||||
return true
|
||||
}
|
||||
|
||||
//寻找第一个文本匹配(关键词)并点击
|
||||
fun findTextAndClick(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean {
|
||||
val textView = findOneByText(nodeInfo, text) ?: return false
|
||||
return performClick(textView)
|
||||
}
|
||||
|
||||
//寻找第一个EditView编辑框并输入文本
|
||||
fun findTextInput(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean {
|
||||
val nodeInfo: AccessibilityNodeInfo = nodeInfo ?: return false
|
||||
val editText = findOneByClazz(nodeInfo, "android.widget.EditText") ?: return false
|
||||
val arguments = Bundle()
|
||||
arguments.putCharSequence(
|
||||
AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
|
||||
text
|
||||
)
|
||||
editText.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
|
||||
return true
|
||||
}
|
||||
|
||||
//寻找第一个列表并点击指定条目(默认点击第一个条目)
|
||||
fun findListOneAndClick(nodeInfo: AccessibilityNodeInfo, index: Int = 0): Boolean {
|
||||
val rv = findOneByClazz(nodeInfo, "androidx.recyclerview.widget.RecyclerView")
|
||||
val lv = findOneByClazz(nodeInfo, "android.widget.ListView")
|
||||
if (rv == null && lv == null) return false
|
||||
if (rv != null && rv.childCount > index) {
|
||||
performClick(rv.getChild(index))
|
||||
} else if (lv != null && lv.childCount > index) {
|
||||
performClick(lv.getChild(index))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//滚动并按文本寻找第一个控件
|
||||
fun scrollAndFindByText(
|
||||
nodeInfo: AccessibilityNodeInfo,
|
||||
text: String,
|
||||
maxRetry: Int = 3
|
||||
): AccessibilityNodeInfo? {
|
||||
var index = 0
|
||||
while (index++ < maxRetry) {
|
||||
performScrollUp(nodeInfo, 0)
|
||||
Thread.sleep(300)
|
||||
val node = findOneByText(nodeInfo, text)
|
||||
if (node != null) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
while (index++ < maxRetry * 2) {
|
||||
performScrollDown(nodeInfo, 0)
|
||||
Thread.sleep(300)
|
||||
val node = findOneByText(nodeInfo, text)
|
||||
if (node != null) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
//输入x, y坐标模拟点击事件
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
fun performXYClick(service: AccessibilityService, x: Float, y: Float) {
|
||||
val path = Path()
|
||||
path.moveTo(x, y)
|
||||
val builder = GestureDescription.Builder()
|
||||
builder.addStroke(GestureDescription.StrokeDescription(path, 0, 1))
|
||||
val gestureDescription = builder.build()
|
||||
service.dispatchGesture(
|
||||
gestureDescription,
|
||||
object : AccessibilityService.GestureResultCallback() {
|
||||
override fun onCompleted(gestureDescription: GestureDescription) {
|
||||
super.onCompleted(gestureDescription)
|
||||
//Log.i(Constant.TAG, "onCompleted: completed");
|
||||
}
|
||||
|
||||
override fun onCancelled(gestureDescription: GestureDescription) {
|
||||
super.onCancelled(gestureDescription)
|
||||
//Log.i(Constant.TAG, "onCancelled: cancelled");
|
||||
}
|
||||
},
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 对某个节点或父节点进行点击
|
||||
*/
|
||||
fun performClick(nodeInfo: AccessibilityNodeInfo?): Boolean {
|
||||
var nodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false
|
||||
while (nodeInfo != null) {
|
||||
if (nodeInfo.isClickable) {
|
||||
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||
return true
|
||||
}
|
||||
nodeInfo = nodeInfo.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 对某个节点或子节点进行点击
|
||||
*/
|
||||
fun performClickWithSon(nodeInfo: AccessibilityNodeInfo?): Boolean {
|
||||
var nodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false
|
||||
while (nodeInfo != null) {
|
||||
if (nodeInfo.isClickable) {
|
||||
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||
return true
|
||||
}
|
||||
if (nodeInfo.childCount > 0) {
|
||||
for (i in 0 until nodeInfo.childCount) {
|
||||
if (performClickWithSon(nodeInfo.getChild(i))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nodeInfo = null
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 对某个节点或父节点进行长按
|
||||
*/
|
||||
fun performLongClick(nodeInfo: AccessibilityNodeInfo?): Boolean {
|
||||
var nodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false
|
||||
while (nodeInfo != null) {
|
||||
if (nodeInfo.isLongClickable) {
|
||||
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK)
|
||||
return true
|
||||
}
|
||||
nodeInfo = nodeInfo.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 对某个节点或子节点进行长按
|
||||
*/
|
||||
fun performLongClickWithSon(nodeInfo: AccessibilityNodeInfo?): Boolean {
|
||||
var nodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false
|
||||
while (nodeInfo != null) {
|
||||
if (nodeInfo.isLongClickable) {
|
||||
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK)
|
||||
return true
|
||||
}
|
||||
if (nodeInfo.childCount > 0) {
|
||||
for (i in 0 until nodeInfo.childCount) {
|
||||
if (performLongClickWithSon(nodeInfo.getChild(i))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nodeInfo = null
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//对某个节点向上滚动
|
||||
fun performScrollUp(nodeInfo: AccessibilityNodeInfo?): Boolean {
|
||||
var nodeInfo: AccessibilityNodeInfo? = nodeInfo ?: return false
|
||||
while (nodeInfo != null) {
|
||||
if (nodeInfo.isScrollable) {
|
||||
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD)
|
||||
return true
|
||||
}
|
||||
nodeInfo = nodeInfo.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//对某个节点或父节点向下滚动
|
||||
fun performScrollDown(node: AccessibilityNodeInfo?): Boolean {
|
||||
var node: AccessibilityNodeInfo? = node ?: return false
|
||||
while (node != null) {
|
||||
if (node.isScrollable) {
|
||||
node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
|
||||
return true
|
||||
}
|
||||
node = node.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//对第几个节点向上滚动
|
||||
fun performScrollUp(node: AccessibilityNodeInfo?, index: Int): Boolean {
|
||||
if (node == null) return false
|
||||
val canScrollNodeList = findCanScrollNode(node)
|
||||
if (canScrollNodeList.size > index) {
|
||||
canScrollNodeList[index].performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//对第几个节点向下滚动
|
||||
fun performScrollDown(node: AccessibilityNodeInfo?, index: Int): Boolean {
|
||||
if (node == null) return false
|
||||
val canScrollNodeList = findCanScrollNode(node)
|
||||
if (canScrollNodeList.size > index) {
|
||||
canScrollNodeList[index].performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//返回可滚动元素集合
|
||||
fun findCanScrollNode(
|
||||
node: AccessibilityNodeInfo?,
|
||||
list: ArrayList<AccessibilityNodeInfo> = ArrayList()
|
||||
): ArrayList<AccessibilityNodeInfo> {
|
||||
if (node == null) return list
|
||||
if (node.isScrollable) list.add(node)
|
||||
for (i in 0 until node.childCount) {
|
||||
findCanScrollNode(node.getChild(i), list)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
//通知栏事件进入应用
|
||||
fun gotoApp(event: AccessibilityEvent) {
|
||||
val data = event.parcelableData
|
||||
if (data != null && data is Notification) {
|
||||
val intent = data.contentIntent
|
||||
try {
|
||||
intent.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//回退
|
||||
fun globalGoBack(service: AccessibilityService) {
|
||||
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
|
||||
}
|
||||
|
||||
//回首页
|
||||
fun globalGoHome(service: AccessibilityService) {
|
||||
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME)
|
||||
}
|
||||
|
||||
//按描述寻找节点和子节点内的一个匹配项
|
||||
fun findOneByDesc(
|
||||
node: AccessibilityNodeInfo?,
|
||||
desc: String,
|
||||
desc2: String? = null
|
||||
): AccessibilityNodeInfo? {
|
||||
if (node == null) return null
|
||||
val description = node.contentDescription?.toString()
|
||||
if (description == desc || (desc2 != null && description == desc2)) {
|
||||
return node
|
||||
}
|
||||
for (i in 0 until node.childCount) {
|
||||
val result = findOneByDesc(node.getChild(i), desc, desc2)
|
||||
if (result != null) return result
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
//按文本(关键词)寻找节点和子节点内的一个匹配项
|
||||
fun findOneByText(
|
||||
node: AccessibilityNodeInfo?,
|
||||
text: String,
|
||||
text2: String? = null
|
||||
): AccessibilityNodeInfo? {
|
||||
if (node == null) return null
|
||||
val textViewList = node.findAccessibilityNodeInfosByText(text)
|
||||
if (textViewList != null && textViewList.size > 0) {
|
||||
for (textView in textViewList) {
|
||||
if (textView.text == text) {
|
||||
return textView
|
||||
}
|
||||
}
|
||||
return textViewList[0]
|
||||
} else if (text2 != null)
|
||||
return findOneByText(node, text2)
|
||||
return null
|
||||
}
|
||||
|
||||
//按文本(关键词)寻找节点和子节点内的所有匹配项
|
||||
fun findAllByText(
|
||||
node: AccessibilityNodeInfo?,
|
||||
text: String,
|
||||
text2: String? = null
|
||||
): List<AccessibilityNodeInfo> {
|
||||
if (node == null) return arrayListOf()
|
||||
val textViewList = node.findAccessibilityNodeInfosByText(text)
|
||||
if (textViewList != null && textViewList.size > 0)
|
||||
return textViewList
|
||||
else if (text2 != null)
|
||||
return findAllByText(node, text2)
|
||||
return arrayListOf()
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类名寻找节点和子节点内的一个匹配项
|
||||
* node 节点
|
||||
* clazz 类名
|
||||
* limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制
|
||||
*/
|
||||
fun findOneByClazz(
|
||||
node: AccessibilityNodeInfo?,
|
||||
clazz: String,
|
||||
limitDepth: Int? = null,
|
||||
depth: Int = 0
|
||||
): AccessibilityNodeInfo? {
|
||||
if (node == null) return null
|
||||
// Log.d(tag, "node.className: " + node.className)
|
||||
if (node.className == clazz) {
|
||||
if (limitDepth == null || limitDepth == depth)
|
||||
return node
|
||||
}
|
||||
for (i in 0 until node.childCount) {
|
||||
val result = findOneByClazz(node.getChild(i), clazz, limitDepth, depth + 1)
|
||||
if (result != null) return result
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类名寻找节点和子节点内的所有匹配项
|
||||
* node 节点
|
||||
* clazz 类名
|
||||
* limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制
|
||||
*/
|
||||
fun findAllByClazz(
|
||||
node: AccessibilityNodeInfo?,
|
||||
clazz: String,
|
||||
list: ArrayList<AccessibilityNodeInfo> = ArrayList()
|
||||
): ArrayList<AccessibilityNodeInfo> {
|
||||
if (node == null) return list
|
||||
// Log.d(tag, "node.className: " + node.className)
|
||||
if (node.className == clazz) list.add(node)
|
||||
for (i in 0 until node.childCount) {
|
||||
findAllByClazz(node.getChild(i), clazz, list)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找节点的前兄弟节点
|
||||
* node 节点
|
||||
*/
|
||||
fun findFrontNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
|
||||
if (node == null) return null
|
||||
// Log.d(tag, "node.className: " + node.className)
|
||||
var parent: AccessibilityNodeInfo? = node.parent
|
||||
var son: AccessibilityNodeInfo? = node
|
||||
while (parent != null) {
|
||||
var index = -1
|
||||
for (i in 0 until parent.childCount) {
|
||||
if (parent.getChild(i) == son) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (index > 0) {
|
||||
return parent.getChild(index - 1)
|
||||
}
|
||||
son = parent
|
||||
parent = parent.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找节点的后兄弟节点
|
||||
* node 节点
|
||||
*/
|
||||
fun findBackNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
|
||||
if (node == null) return null
|
||||
// Log.d(tag, "node.className: " + node.className)
|
||||
var parent: AccessibilityNodeInfo? = node.parent
|
||||
var son: AccessibilityNodeInfo? = node
|
||||
while (parent != null) {
|
||||
var index = -1
|
||||
for (i in 0 until parent.childCount) {
|
||||
if (parent.getChild(i) == son) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (index < parent.childCount - 1) {
|
||||
return parent.getChild(index + 1)
|
||||
}
|
||||
son = parent
|
||||
parent = parent.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度搜索打印节点及其子节点
|
||||
* node 节点
|
||||
*/
|
||||
fun printNodeClazzTree(
|
||||
node: AccessibilityNodeInfo?,
|
||||
printText: Boolean = true,
|
||||
depth: Int = 0
|
||||
) {
|
||||
if (node == null) return
|
||||
var s = ""
|
||||
for (i in 0 until depth) {
|
||||
s += "---"
|
||||
}
|
||||
Log.d(tag, "$s depth: $depth className: " + node.className)
|
||||
if (printText && node.text != null) {
|
||||
Log.d(tag, "$s depth: $depth text: " + node.text)
|
||||
}
|
||||
if (printText && node.contentDescription != null) {
|
||||
Log.d(tag, "$s depth: $depth desc: " + node.contentDescription)
|
||||
}
|
||||
for (i in 0 until node.childCount) {
|
||||
printNodeClazzTree(node.getChild(i), printText, depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
51
app/src/main/java/org/yameida/worktool/utils/OkHttpUtil.kt
Normal file
@@ -0,0 +1,51 @@
|
||||
package org.yameida.worktool.utils
|
||||
|
||||
import okhttp3.*
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
66
app/src/main/java/org/yameida/worktool/utils/UpdateUtil.kt
Normal file
@@ -0,0 +1,66 @@
|
||||
package org.yameida.worktool.utils
|
||||
|
||||
import com.blankj.utilcode.util.AppUtils
|
||||
import com.blankj.utilcode.util.GsonUtils
|
||||
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 model.UpdateConfig
|
||||
import org.yameida.worktool.Constant
|
||||
import org.yameida.worktool.R
|
||||
import org.yameida.worktool.model.network.CheckUpdateResult
|
||||
import update.UpdateAppUtils
|
||||
|
||||
object UpdateUtil {
|
||||
|
||||
fun checkUpdate() {
|
||||
// val remoteVersionCode = 10
|
||||
// val remoteVersionName = "1.0.1"
|
||||
// val forceUpdate = false
|
||||
// val updateLog = "修复Bug\n优化用户体验"
|
||||
// val downloadUrl = "https://down.qq.com/qqweb/QQ_1/android_apk/Android_8.5.0.5025_537066738.apk"
|
||||
// val fileMD5 = "560017dc94e8f9b65f4ca997c7feb326"
|
||||
OkGo.get<String>(Constant.URL_CHECK_UPDATE)
|
||||
.execute(object : StringCallback() {
|
||||
override fun onSuccess(response: Response<String>) {
|
||||
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
|
||||
)
|
||||
)
|
||||
.update()
|
||||
} else {
|
||||
ToastUtils.showShort(R.string.update_no_update)
|
||||
}
|
||||
return
|
||||
}
|
||||
ToastUtils.showLong(R.string.update_failed)
|
||||
}
|
||||
|
||||
override fun onError(response: Response<String>) {
|
||||
ToastUtils.showLong(R.string.update_failed)
|
||||
LogUtils.e("检查更新失败")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
15
app/src/main/java/org/yameida/worktool/utils/Views.java
Normal file
@@ -0,0 +1,15 @@
|
||||
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";
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.yameida.worktool.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.blankj.utilcode.util.GsonUtils;
|
||||
import com.blankj.utilcode.util.LogUtils;
|
||||
|
||||
import org.yameida.worktool.model.WeworkMessageBean;
|
||||
import org.yameida.worktool.model.WeworkMessageListBean;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
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 +
|
||||
",\"hearBeat\": \"心跳检测\"" +
|
||||
"}";
|
||||
private static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
||||
public static Map<String, WebSocketManager> webSocketManager = new ConcurrentHashMap<>();
|
||||
private static final int reconnectTimes = 7;
|
||||
private static final int reconnectInt = 5000; //毫秒
|
||||
private static final long heartBeatRate = 15; //秒
|
||||
private Map<String, Long> messageIdMap = new ConcurrentHashMap<>();
|
||||
private ScheduledFuture task;
|
||||
private WebSocket socket;
|
||||
private String url;
|
||||
private WebSocketListener listener;
|
||||
private boolean connecting = false;
|
||||
|
||||
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);
|
||||
webSocketManager.put(url, this);
|
||||
task = heartCheckStart();
|
||||
}
|
||||
|
||||
public void send(WeworkMessageBean msg) {
|
||||
send(new WeworkMessageListBean(msg, WeworkMessageListBean.SOCKET_TYPE_MESSAGE_LIST));
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认消息
|
||||
* @param messageId 保存在map中30秒后清除
|
||||
* @return true继续消费事件 false重复事件不需消息
|
||||
*/
|
||||
public synchronized boolean confirm(String messageId) {
|
||||
if (messageId == null || messageId.isEmpty()) return true;
|
||||
send(new WeworkMessageListBean(messageId, WeworkMessageListBean.SOCKET_TYPE_MESSAGE_CONFIRM));
|
||||
if (messageIdMap.containsKey(messageId)) return false;
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
messageIdMap.put(messageId, 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, false);
|
||||
}
|
||||
|
||||
public void send(WeworkMessageListBean msg, boolean log) {
|
||||
String json = GsonUtils.toJson(msg);
|
||||
boolean suc = socket.send(json);
|
||||
if (log)
|
||||
LogUtils.v(url, json, (suc ? "通讯消息发送成功!" : "通讯消息发送失败!"));
|
||||
else
|
||||
LogUtils.e(url, json, (suc ? "通讯消息发送成功!" : "通讯消息发送失败!"));
|
||||
}
|
||||
|
||||
public void send(String msg) {
|
||||
boolean suc = socket.send(msg);
|
||||
LogUtils.e(url, msg, (suc ? "通讯消息发送成功!" : "通讯消息发送失败!"));
|
||||
}
|
||||
|
||||
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;
|
||||
while (!isConnect) {
|
||||
try {
|
||||
isConnect = connect();
|
||||
Thread.sleep(reconnectInt);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
connecting = false;
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
WebSocket s = new OkHttpClient().newWebSocket(new Request.Builder().url(url).build(), listener);
|
||||
if (s.send(WebSocketManager.HEARTBEAT)) {
|
||||
this.socket = s;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ScheduledFuture heartCheckStart() {
|
||||
Runnable r = () -> {
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
|
||||
Log.e(url, "心跳检测" + df.format(new Date()));// new Date()为获取当前系统时间
|
||||
if (!connecting && (socket == null || !socket.send(HEARTBEAT))) {
|
||||
reConnect();
|
||||
}
|
||||
};
|
||||
|
||||
//每heartBeatRate秒发一次心跳包
|
||||
return scheduledExecutorService.scheduleAtFixedRate(r, heartBeatRate, heartBeatRate, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static WebSocketManager getWebSocketManager(String id) {
|
||||
return webSocketManager.get(id);
|
||||
}
|
||||
}
|
||||
250
app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt
Normal file
@@ -0,0 +1,250 @@
|
||||
package org.yameida.worktool.utils
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import org.yameida.worktool.utils.AccessibilityUtil.findOneByClazz
|
||||
import org.yameida.worktool.utils.AccessibilityUtil.findFrontNode
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import org.yameida.worktool.service.backPress
|
||||
import org.yameida.worktool.service.getRoot
|
||||
import org.yameida.worktool.service.goHome
|
||||
import org.yameida.worktool.service.sleep
|
||||
import org.yameida.worktool.utils.AccessibilityUtil.findAllByClazz
|
||||
|
||||
/**
|
||||
* 房间特征分析工具类
|
||||
*/
|
||||
object WeworkRoomUtil {
|
||||
|
||||
/**
|
||||
* 房间类型 ROOM_TYPE
|
||||
* @see WeworkMessageBean.ROOM_TYPE
|
||||
*/
|
||||
fun getRoomType(root: AccessibilityNodeInfo): Int {
|
||||
when {
|
||||
isExternalSingleChat(root) -> {
|
||||
LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_CONTACT")
|
||||
return WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT
|
||||
}
|
||||
isGroupChat(root) -> {
|
||||
return if (isExternalGroup(root)) {
|
||||
LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_GROUP")
|
||||
WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP
|
||||
} else {
|
||||
LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_GROUP")
|
||||
WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP
|
||||
}
|
||||
}
|
||||
isSingleChat(root) -> {
|
||||
LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_CONTACT")
|
||||
return WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT
|
||||
}
|
||||
else -> {
|
||||
LogUtils.d("ROOM_TYPE: ROOM_TYPE_UNKNOWN")
|
||||
return WeworkMessageBean.ROOM_TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间类型
|
||||
* @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 getRoomTitle(root: AccessibilityNodeInfo): ArrayList<String> {
|
||||
val titleList = arrayListOf<String>()
|
||||
val list = findOneByClazz(root, Views.ListView)
|
||||
if (list != null) {
|
||||
val frontNode = findFrontNode(list.parent.parent)
|
||||
val textViewList = findAllByClazz(frontNode, Views.TextView)
|
||||
for (textView in textViewList) {
|
||||
if (!textView.text.isNullOrBlank()) {
|
||||
titleList.add(textView.text.toString().replace("\\(\\d+\\)$".toRegex(), ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
LogUtils.d("getRoomTitle: ", titleList)
|
||||
return titleList
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入房间(单聊或群聊)
|
||||
*/
|
||||
fun intoRoom(title: String): Boolean {
|
||||
LogUtils.d("intoRoom(): $title")
|
||||
val titleList = getRoomTitle(getRoot())
|
||||
val roomType = getRoomType(getRoot())
|
||||
if (roomType != WeworkMessageBean.ROOM_TYPE_UNKNOWN
|
||||
&& titleList.count { it.replace("…", "") == title.replace("…", "") } > 0) {
|
||||
LogUtils.d("当前正在房间")
|
||||
return true
|
||||
}
|
||||
goHome()
|
||||
val list = findOneByClazz(getRoot(), Views.ListView)
|
||||
if (list != null) {
|
||||
val frontNode = findFrontNode(list)
|
||||
val textViewList = findAllByClazz(frontNode, Views.TextView)
|
||||
if (textViewList.size >= 2) {
|
||||
val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2]
|
||||
val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1]
|
||||
AccessibilityUtil.performClick(searchButton)
|
||||
sleep(1000)
|
||||
AccessibilityUtil.findTextInput(getRoot(), title.replace("…", ""))
|
||||
sleep(1000)
|
||||
var selectListView: AccessibilityNodeInfo? = null
|
||||
while (selectListView == null) {
|
||||
LogUtils.d("未找到搜索结果列表")
|
||||
selectListView = findOneByClazz(getRoot(), Views.ListView)
|
||||
sleep(500)
|
||||
}
|
||||
val imageView = findOneByClazz(selectListView, Views.ImageView)
|
||||
if (imageView != null) {
|
||||
AccessibilityUtil.performClick(imageView)
|
||||
}
|
||||
sleep(2000)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("未找到搜索按钮")
|
||||
}
|
||||
}
|
||||
LogUtils.e("未找到聊天列表")
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入群管理页
|
||||
* @return true 成功进入群管理页
|
||||
*/
|
||||
fun intoGroupManager(): Boolean {
|
||||
if (AccessibilityUtil.findOneByText(
|
||||
getRoot(),
|
||||
"由企业微信用户创建,可邀请微信用户进群",
|
||||
"该群由企业微信用户创建"
|
||||
) != null) {
|
||||
return true
|
||||
}
|
||||
val list = findOneByClazz(getRoot(), Views.ListView)
|
||||
if (list != null) {
|
||||
val frontNode = AccessibilityUtil.findFrontNode(list.parent.parent)
|
||||
val textViewList = findAllByClazz(frontNode, Views.TextView)
|
||||
if (textViewList.size >= 2) {
|
||||
val multiButton = textViewList.lastOrNull()
|
||||
AccessibilityUtil.performClick(multiButton)
|
||||
sleep(2000)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("未找到群管理按钮")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入好友详情页
|
||||
* @return true 成功进入好友详情页
|
||||
*/
|
||||
fun intoFriendDetail(): Boolean {
|
||||
if (AccessibilityUtil.findOneByText(getRoot(), "设置聊天背景") != null) {
|
||||
return true
|
||||
}
|
||||
val list = findOneByClazz(getRoot(), Views.ListView)
|
||||
if (list != null) {
|
||||
val frontNode = AccessibilityUtil.findFrontNode(list.parent.parent)
|
||||
val textViewList = findAllByClazz(frontNode, Views.TextView)
|
||||
if (textViewList.size >= 2) {
|
||||
val multiButton = textViewList.lastOrNull()
|
||||
AccessibilityUtil.performClick(multiButton)
|
||||
sleep(2000)
|
||||
return true
|
||||
} else {
|
||||
LogUtils.e("未找到好友详情按钮")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前聊天人姓名并返回房间
|
||||
* 解决title为对方正在输入中问题
|
||||
* @return name 单聊对方姓名
|
||||
*/
|
||||
fun getFriendName(): ArrayList<String> {
|
||||
val titleList = arrayListOf<String>()
|
||||
if (intoFriendDetail()) {
|
||||
val gridView = findOneByClazz(getRoot(), Views.GridView)
|
||||
if (gridView != null && gridView.childCount >= 2) {
|
||||
val tvList = findAllByClazz(gridView.getChild(0), Views.TextView)
|
||||
for (textView in tvList) {
|
||||
if (textView.text != null) {
|
||||
titleList.add(textView.text.toString())
|
||||
backPress()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return titleList
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是群聊
|
||||
* 群右上角有两个按钮(快速会议按钮、更多按钮)
|
||||
*/
|
||||
private fun isGroupChat(root: AccessibilityNodeInfo): Boolean {
|
||||
val list = findOneByClazz(root, Views.ListView)
|
||||
if (list != null) {
|
||||
val frontNode = findFrontNode(list.parent.parent)
|
||||
val textViewList = findAllByClazz(frontNode, Views.TextView)
|
||||
if (textViewList.size >= 2) {
|
||||
val buttonList = findAllByClazz(textViewList.last().parent.parent, Views.TextView)
|
||||
return buttonList.size == 2
|
||||
} else {
|
||||
LogUtils.d("未找到群管理按钮")
|
||||
}
|
||||
} else {
|
||||
LogUtils.d("未找到消息列表")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是外部群
|
||||
* listview前兄弟控件 && text包含外部群
|
||||
*/
|
||||
private fun isExternalGroup(root: AccessibilityNodeInfo): Boolean {
|
||||
val listView = findOneByClazz(root, Views.ListView, null, 0)
|
||||
if (listView != null) {
|
||||
val frontNode = findFrontNode(listView)
|
||||
if (frontNode != null) {
|
||||
val nodeList = frontNode.findAccessibilityNodeInfosByText("外部群")
|
||||
return nodeList.size > 0
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是单聊
|
||||
* 有列表和输入框
|
||||
*/
|
||||
private fun isSingleChat(root: AccessibilityNodeInfo): Boolean {
|
||||
val list = findOneByClazz(root, Views.ListView)
|
||||
val editText = findOneByClazz(root, Views.EditText)
|
||||
if (list != null && editText != null) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是外部单聊
|
||||
* 姓名下面有@xx
|
||||
*/
|
||||
private fun isExternalSingleChat(root: AccessibilityNodeInfo): Boolean {
|
||||
val roomTitle = getRoomTitle(root)
|
||||
return roomTitle.size > 1 && roomTitle.count { it.matches("^[@@].*?".toRegex()) } > 0
|
||||
}
|
||||
|
||||
}
|
||||
339
app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt
Normal file
@@ -0,0 +1,339 @@
|
||||
package org.yameida.worktool.utils
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import org.yameida.worktool.model.WeworkMessageBean
|
||||
import org.yameida.worktool.service.getRoot
|
||||
import org.yameida.worktool.service.sleep
|
||||
import org.yameida.worktool.utils.AccessibilityUtil.findOneByClazz
|
||||
import org.yameida.worktool.utils.AccessibilityUtil.findAllByClazz
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 消息特征分析工具类
|
||||
*/
|
||||
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 text: 企微RPA机器人:
|
||||
--------------------- 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: 10 text: 新公告
|
||||
--------------- depth: 5 className: android.widget.TextView
|
||||
--------------- depth: 5 text: 111
|
||||
|
||||
------------------------------总结------------------------------
|
||||
图片 0tv 1iv (图片)
|
||||
视频 2tv 2iv (视频大小、视频时长、缩略图、播放按钮)
|
||||
链接 3tv 1iv (标题、副标题、下方来源、图标)
|
||||
文件 3tv 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 {
|
||||
val tvList = findAllByClazz(node, Views.TextView)
|
||||
val tvCount = tvList.size
|
||||
val ivCount = findAllByClazz(node, Views.ImageView).size
|
||||
return when {
|
||||
tvCount == 1 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_PLAIN
|
||||
tvCount == 0 && ivCount == 1 -> WeworkMessageBean.TEXT_TYPE_IMAGE
|
||||
tvCount == 2 && ivCount == 2 -> {
|
||||
if (tvList[0].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 == 1 -> WeworkMessageBean.TEXT_TYPE_FILE
|
||||
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 -> WeworkMessageBean.TEXT_TYPE_SOLITAIRE
|
||||
tvCount == 4 && ivCount == 2 -> WeworkMessageBean.TEXT_TYPE_VOICE
|
||||
tvCount == 5 && ivCount == 1 -> WeworkMessageBean.TEXT_TYPE_CARD
|
||||
tvCount == 1 && ivCount == 2 -> WeworkMessageBean.TEXT_TYPE_LOCATION
|
||||
tvCount == 3 && ivCount == 0 -> WeworkMessageBean.TEXT_TYPE_REPLY
|
||||
else -> WeworkMessageBean.TEXT_TYPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为消息上方时间
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 群聊 提取发言人昵称
|
||||
* 适用于左侧发言者
|
||||
* @param item 消息item节点
|
||||
*/
|
||||
fun getNameList(item: AccessibilityNodeInfo): List<String> {
|
||||
val nameList = ArrayList<String>()
|
||||
val node = findOneByClazz(item, Views.ViewGroup)
|
||||
if (node != null) {
|
||||
val textViewList = findAllByClazz(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,
|
||||
key: String
|
||||
): Boolean {
|
||||
if (node == null) return false
|
||||
for (i in 0 until node.childCount) {
|
||||
val item = node.getChild(i)
|
||||
val nameList = getNameList(item)
|
||||
for (name in nameList.reversed()) {
|
||||
if (name == replyNick) {
|
||||
val backNode = getMessageListNode(item)
|
||||
if (backNode != null) {
|
||||
val textNode = AccessibilityUtil.findOneByText(backNode, replyContent)
|
||||
if (textNode != null) {
|
||||
LogUtils.d("nameList: $nameList\nreplyContent: $replyContent")
|
||||
return longClickMessageItem(item, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun longClickMessageItem(item: AccessibilityNodeInfo, key: String): Boolean {
|
||||
val backNode = getMessageListNode(item)
|
||||
AccessibilityUtil.performLongClickWithSon(backNode)
|
||||
sleep(1000)
|
||||
val optionRvList = findAllByClazz(getRoot(), Views.RecyclerView)
|
||||
for (optionRv in optionRvList) {
|
||||
val optionTvList = findAllByClazz(optionRv, Views.TextView)
|
||||
for (optionTv in optionTvList) {
|
||||
if (optionTv.text == key) {
|
||||
AccessibilityUtil.performClick(optionTv)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 群聊 提取消息主体框节点(昵称下面的气泡框)
|
||||
* 适用于左侧发言者
|
||||
* @param item 消息item节点
|
||||
*/
|
||||
private fun getMessageListNode(item: AccessibilityNodeInfo): AccessibilityNodeInfo? {
|
||||
val node = findOneByClazz(item, Views.ViewGroup)
|
||||
if (node != null) {
|
||||
return AccessibilityUtil.findBackNode(node)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
22
app/src/main/res/drawable/comment_red_btn.xml
Normal file
@@ -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>
|
||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
292
app/src/main/res/layout/activity_listen.xml
Normal file
@@ -0,0 +1,292 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<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>
|
||||
|
||||
<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_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: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="请输入申请的链接号"
|
||||
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:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="50dp"
|
||||
android:paddingEnd="50dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/comment_red_btn"
|
||||
android:id="@+id/bt_save"
|
||||
android:text="保存" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</RelativeLayout>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
50
app/src/main/res/values/colors-rec.xml
Normal file
@@ -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>
|
||||
6
app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#6200EE</color>
|
||||
<color name="colorPrimaryDark">#000000</color>
|
||||
<color name="colorAccent">#03DAC5</color>
|
||||
</resources>
|
||||
32
app/src/main/res/values/dimens.xml
Normal file
@@ -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">7dp</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>
|
||||
22
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<resources>
|
||||
<string name="app_name">WorkTool</string>
|
||||
|
||||
<string name="accessibility_desc">WorkTool</string>
|
||||
<string name="tips">如果需要使用或停止该应用,应用需打开 无障碍服务 进行权限设置</string>
|
||||
|
||||
<!-- 升级对话框 -->
|
||||
<string name="update_title" translatable="false">发现新版本</string>
|
||||
<string name="update_content" translatable="false">更新内容</string>
|
||||
<string name="update_no" translatable="false">下次再说</string>
|
||||
<string name="update_yes" translatable="false">立即更新</string>
|
||||
<string name="update_permission_hint" translatable="false">必须先要授予权限才能正常下载更新哦</string>
|
||||
<string name="update_status_start" translatable="false">正在下载</string>
|
||||
<string name="update_status_running" translatable="false">下载中 %d%%</string>
|
||||
<string name="update_status_successful" translatable="false">下载完成,点击安装</string>
|
||||
<string name="update_status_failed" translatable="false">下载失败,点击重试</string>
|
||||
<string name="update_failed" translatable="false">自动更新失败</string>
|
||||
<string name="update_no_update" translatable="false">当前已是最新版本</string>
|
||||
<string name="update_notification_channel_id" translatable="false">update</string>
|
||||
<string name="update_notification_channel_name" translatable="false">升级通知</string>
|
||||
|
||||
</resources>
|
||||
15
app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
10
app/src/main/res/xml/accessibility_service_config.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:description="@string/accessibility_desc"
|
||||
android:packageNames="com.tencent.wework"
|
||||
android:accessibilityEventTypes="typeAllMask"
|
||||
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
|
||||
android:accessibilityFeedbackType="feedbackAllMask"
|
||||
android:notificationTimeout="100"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:canPerformGestures="true"/>
|
||||
4
app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true" />
|
||||
</network-security-config>
|
||||