update 封装base库;集成flowwindow库;悬浮窗功能;赞助和分享;集成企微sdk;界面更新;添加待办修复;滚动手势优化;建群达到上限检查;兼容减号和括号搜索;应用保活;其他已知缺陷修复

This commit is contained in:
gallonyin
2022-12-09 16:15:49 +08:00
parent b4b5746f5c
commit 3636ffdc2f
89 changed files with 3908 additions and 272 deletions

View File

@@ -24,6 +24,7 @@
<application
android:name="org.yameida.worktool.MyApplication"
android:allowBackup="true"
tools:replace="android:allowBackup"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
@@ -36,6 +37,7 @@
<activity
android:name="org.yameida.worktool.activity.ListenActivity"
android:windowSoftInputMode="adjustUnspecified|stateHidden"
android:launchMode="singleTask"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -44,7 +46,24 @@
</activity>
<activity
android:name="org.yameida.worktool.activity.LoginActivity"
android:launchMode="singleInstance"
android:theme="@style/AppTheme">
</activity>
<activity
android:name="org.yameida.worktool.activity.SettingsActivity"
android:launchMode="singleInstance"
android:theme="@style/AppTheme">
</activity>
<activity
android:name="org.yameida.worktool.activity.BrowserActivity"
android:windowSoftInputMode="adjustUnspecified|stateHidden"
android:launchMode="singleInstance"
android:theme="@style/AppTheme">
</activity>
<activity
android:name="org.yameida.worktool.activity.FloatViewGuideActivity"
android:windowSoftInputMode="adjustUnspecified|stateHidden"
android:launchMode="singleInstance"
android:theme="@style/AppTheme">
</activity>
<service

View File

@@ -4,16 +4,16 @@ import com.blankj.utilcode.util.SPUtils
object Constant {
val AVAILABLE_VERSION = arrayListOf("4.0.2", "4.0.6", "4.0.8", "4.0.10", "4.0.12", "4.0.16", "4.0.18", "4.0.19")
val AVAILABLE_VERSION = arrayListOf("4.0.2", "4.0.6", "4.0.8", "4.0.10", "4.0.12", "4.0.16", "4.0.18", "4.0.19", "4.0.20")
const val PACKAGE_NAMES = "com.tencent.wework"
const val LISTEN_CHANNEL_ID = "LISTEN_CHANNEL_ID"
const val WEWORK_NOTIFY = "wework_notify"
const val CHANGE_PAGE_INTERVAL = 1000L
const val POP_WINDOW_INTERVAL = 500L
private const val DEFAULT_HOST = "wss://worktool.asrtts.cn"
var myName = ""
var regTrimTitle = "(…$)|(-.*$)|(\\(.*?\\)$)".toRegex()
// var regTrimTitle = "(…$)|(-.*$)|(\\(.*?\\)$)".toRegex()
var regTrimTitle = "(…$)".toRegex()
var key = "9876543210abcdef".toByteArray()
var iv = "0123456789abcdef".toByteArray()
val transformation = "AES/CBC/PKCS7Padding"
@@ -21,16 +21,33 @@ object Constant {
var autoReply = SPUtils.getInstance().getInt("autoReply", 1)
var groupStrict = false
var friendRemarkStrict = false
var robotId: String
get() = SPUtils.getInstance().getString("robotId", SPUtils.getInstance().getString("LISTEN_CHANNEL_ID", ""))
set(value) {
SPUtils.getInstance().put("robotId", value)
}
var replyStrategy: Int
get() = SPUtils.getInstance().getInt("replyStrategy", 1)
set(value) {
SPUtils.getInstance().put("replyStrategy", value)
}
var qaUrl: String
get() = SPUtils.getInstance().getString("qaUrl", "")
set(value) {
SPUtils.getInstance().put("qaUrl", value)
}
var host: String
get() = SPUtils.getInstance().getString("host", DEFAULT_HOST)
set(value) {
SPUtils.getInstance().put("host", value)
}
fun getWsUrl() = "$host/webserver/wework/" + SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID)
fun getWsUrl() = "$host/webserver/wework/$robotId"
fun getCheckUpdateUrl() = "${getBaseUrl()}/appUpdate/checkUpdate"
fun getRobotUpdateUrl() = "${getBaseUrl()}/robot/robotInfo/update"
fun getTestUrl() = "${getBaseUrl()}/test"
private fun getBaseUrl() = host.replace("wss", "https").replace("ws", "http")

View File

@@ -12,6 +12,7 @@ import com.hjq.toast.ToastUtils
import com.tendcloud.tenddata.TalkingDataSDK
import com.umeng.commonsdk.UMConfigure
import org.yameida.worktool.config.GlobalException
import org.yameida.worktool.utils.IWWAPIUtil
import update.UpdateAppUtils
class MyApplication : Application() {
@@ -46,7 +47,9 @@ class MyApplication : Application() {
if (SPUtils.getInstance().getString("uminit", "1") == "1") {
UMConfigure.init(this, key, channel, UMConfigure.DEVICE_TYPE_PHONE, "")
}
TalkingDataSDK.init(this, "80E9C84E39904DAFB28562910FF7C86C", "worktool_master", SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID));
TalkingDataSDK.init(this, "80E9C84E39904DAFB28562910FF7C86C", "worktool_master", Constant.robotId);
//初始化企业微信sdk
IWWAPIUtil.init(this)
//初始化自动更新
UpdateAppUtils.init(this)
//设置全局异常捕获重启

View File

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

View File

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

View File

@@ -4,14 +4,12 @@ import android.os.Bundle
import android.provider.Settings
import android.view.WindowManager
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 android.content.*
import android.text.InputType
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -28,7 +26,9 @@ class ListenActivity : AppCompatActivity() {
* @param type 0=游客登录
*/
fun enterActivity(context: Context, type: Int) {
LogUtils.d("ListenActivity.enterActivity type: $type")
context.startActivity(Intent(context, ListenActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
putExtra("type", type)
})
}
@@ -56,10 +56,7 @@ class ListenActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
sw_overlay.isChecked = PermissionUtils.isGrantedDrawOverlays()
freshOpenServiceSwitch(
WeworkService::class.java,
sw_accessibility
)
freshOpenServiceSwitch()
if (needToWork) {
needToWork = false
goToWork()
@@ -67,28 +64,19 @@ class ListenActivity : AppCompatActivity() {
}
private fun initView() {
et_channel.setText(SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID))
iv_settings.setOnClickListener {
SettingsActivity.enterActivity(this)
}
et_channel.setText(Constant.robotId)
bt_save.setOnClickListener {
val channel = et_channel.text.toString().trim()
SPUtils.getInstance().put(Constant.LISTEN_CHANNEL_ID, channel)
Constant.robotId = channel
ToastUtils.showLong("保存成功")
sendBroadcast(Intent(Constant.WEWORK_NOTIFY).apply {
putExtra("type", "modify_channel")
})
MobclickAgent.onProfileSignIn(channel)
}
sw_encrypt.isChecked = Constant.encryptType == 1
sw_encrypt.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_encrypt onCheckedChanged: $isChecked")
Constant.encryptType = if (isChecked) 1 else 0
SPUtils.getInstance().put("encryptType", Constant.encryptType)
})
sw_auto_reply.isChecked = Constant.autoReply == 1
sw_auto_reply.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_auto_reply onCheckedChanged: $isChecked")
Constant.autoReply = if (isChecked) 1 else 0
SPUtils.getInstance().put("autoReply", Constant.autoReply)
})
tv_host.text = Constant.host
tv_host.setOnClickListener {
showSelectHostDialog()
@@ -134,7 +122,7 @@ class ListenActivity : AppCompatActivity() {
sw_accessibility.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_accessibility onCheckedChanged: $isChecked")
if (isChecked) {
if (SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID).isNullOrBlank()) {
if (Constant.robotId.isBlank()) {
sw_accessibility.isChecked = false
ToastUtils.showLong("请先填写并保存链接号~")
} else if (!PermissionHelper.isAccessibilitySettingOn()) {
@@ -183,24 +171,15 @@ class ListenActivity : AppCompatActivity() {
private fun openAccessibility() {
val clickListener =
DialogInterface.OnClickListener { dialog, which ->
freshOpenServiceSwitch(
WeworkService::class.java,
sw_accessibility
)
freshOpenServiceSwitch()
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
}
val cancel = DialogInterface.OnCancelListener {
freshOpenServiceSwitch(
WeworkService::class.java,
sw_accessibility
)
freshOpenServiceSwitch()
}
val cancelListener = DialogInterface.OnClickListener { dialog, which ->
freshOpenServiceSwitch(
WeworkService::class.java,
sw_accessibility
)
freshOpenServiceSwitch()
}
val dialog: AlertDialog = AlertDialog.Builder(this)
.setMessage(R.string.tips)
@@ -211,20 +190,8 @@ class ListenActivity : AppCompatActivity() {
dialog.show()
}
private fun freshOpenServiceSwitch(clazz: Class<*>, s: Switch) {
if (PermissionHelper.isAccessibilitySettingOn()) {
s.isChecked = true
when (s.id) {
R.id.sw_accessibility -> {
}
}
} else {
s.isChecked = false
when (s.id) {
R.id.sw_accessibility -> {
}
}
}
private fun freshOpenServiceSwitch() {
sw_accessibility.isChecked = PermissionHelper.isAccessibilitySettingOn()
}
private fun showSelectHostDialog() {

View File

@@ -0,0 +1,279 @@
package org.yameida.worktool.activity
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.text.InputType
import android.view.WindowManager
import android.widget.CompoundButton
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.blankj.utilcode.util.*
import com.lzy.okgo.OkGo
import com.lzy.okgo.callback.StringCallback
import com.lzy.okgo.model.Response
import com.qmuiteam.qmui.widget.dialog.QMUIDialog
import kotlinx.android.synthetic.main.activity_settings.*
import okhttp3.MediaType
import okhttp3.RequestBody
import org.json.JSONObject
import org.yameida.worktool.Constant
import org.yameida.worktool.R
import org.yameida.worktool.utils.*
/**
* 登录页
*/
class SettingsActivity : AppCompatActivity() {
companion object {
fun enterActivity(context: Context) {
LogUtils.d("SettingsActivity.enterActivity")
context.startActivity(Intent(context, SettingsActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
setContentView(R.layout.activity_settings)
initView()
}
override fun onResume() {
super.onResume()
freshOpenFlow()
freshOpenMain()
}
private fun initView() {
iv_back_left.setOnClickListener { finish() }
sw_encrypt.isChecked = Constant.encryptType == 1
sw_encrypt.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_encrypt onCheckedChanged: $isChecked")
Constant.encryptType = if (isChecked) 1 else 0
SPUtils.getInstance().put("encryptType", Constant.encryptType)
})
sw_receive.isChecked = Constant.autoReply == 1
sw_receive.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
LogUtils.i("sw_receive onCheckedChanged: $isChecked")
Constant.autoReply = if (isChecked) 1 else 0
SPUtils.getInstance().put("autoReply", Constant.autoReply)
})
rl_reply_strategy.setOnClickListener { showReplyStrategyDialog() }
rl_qa_url.setOnClickListener { showQaUrlDialog() }
rl_donate.setOnClickListener { showDonateDialog() }
rl_share.setOnClickListener { showShareDialog() }
freshOpenFlow()
bt_open_flow.setOnClickListener {
freshOpenFlow()
if (Settings.canDrawOverlays(Utils.getApp())) {
if (!FlowPermissionHelper.canBackgroundStart(Utils.getApp())) {
ToastUtils.showLong("请同时打开后台弹出界面权限~")
PermissionPageManagement.goToSetting(this)
}
FloatWindowHelper.showWindow()
} else {
startActivityForResult(Intent(this, FloatViewGuideActivity::class.java), 0)
}
}
freshOpenMain()
bt_open_main.setOnClickListener {
freshOpenMain()
if (PermissionHelper.isAccessibilitySettingOn()) {
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
} else {
if (Constant.robotId.isBlank()) {
ToastUtils.showLong("请先填写并保存链接号~")
} else if (!PermissionHelper.isAccessibilitySettingOn()) {
openAccessibility()
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
freshOpenFlow()
if (Settings.canDrawOverlays(Utils.getApp())) {
if (!FlowPermissionHelper.canBackgroundStart(Utils.getApp())) {
ToastUtils.showLong("请同时打开后台弹出界面权限~")
PermissionPageManagement.goToSetting(this)
}
FloatWindowHelper.showWindow()
}
}
private fun showReplyStrategyDialog() {
val strategyArray = arrayOf("只读消息不回调", "仅私聊和群聊@机器人回调", "私聊群聊全部回调")
QMUIDialog.CheckableDialogBuilder(this)
.setTitle("回复策略")
.addItems(strategyArray) { dialog, which ->
dialog.dismiss()
updateRobotReplyStrategy(which)
}
.setCheckedIndex(Constant.replyStrategy)
.create(R.style.QMUI_Dialog)
.show()
}
private fun showQaUrlDialog() {
val builder = QMUIDialog.EditTextDialogBuilder(this)
builder.setTitle("消息回调地址")
.setPlaceholder("请输入回调接口地址")
.setDefaultText(Constant.qaUrl)
.setInputType(InputType.TYPE_CLASS_TEXT)
.addAction(getString(R.string.delete)) { dialog, index ->
dialog.dismiss()
updateRobotQaUrl("")
}
.addAction(getString(R.string.cancel)) { dialog, index -> dialog.dismiss() }
.addAction(getString(R.string.add)) { dialog, index ->
val text = builder.editText.text
if (text != null) {
if (text.matches("https?://[^/]+.*".toRegex())) {
dialog.dismiss()
updateRobotQaUrl(text.toString().trim())
} else {
ToastUtils.showLong("格式异常!")
}
} else {
ToastUtils.showLong("请勿为空!")
}
}
.create(R.style.QMUI_Dialog).show()
}
private fun showDonateDialog() {
DonateUtil.zfbDonate(this)
}
private fun showShareDialog() {
startActivity(Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
type = ShareUtil.TEXT
putExtra(Intent.EXTRA_TEXT, "我发现一个非常好用的企业微信机器人程序,文档地址: https://worktool.apifox.cn/ APP下载地址是: https://cdn.asrtts.cn/uploads/worktool/apk/worktool-latest.apk")
}, "分享"))
}
private fun freshOpenFlow() {
if (Settings.canDrawOverlays(Utils.getApp())) {
if (FlowPermissionHelper.canBackgroundStart(Utils.getApp())) {
bt_open_flow.setBackgroundResource(R.drawable.comment_gray_btn)
bt_open_flow.text = "悬浮窗权限已开启"
} else {
bt_open_flow.setBackgroundResource(R.drawable.comment_red_btn)
bt_open_flow.text = "开启后台弹出界面"
}
} else {
bt_open_flow.setBackgroundResource(R.drawable.comment_red_btn)
bt_open_flow.text = "开启悬浮窗权限"
}
}
private fun freshOpenMain() {
if (PermissionHelper.isAccessibilitySettingOn()) {
bt_open_main.setBackgroundResource(R.drawable.comment_gray_btn)
bt_open_main.text = "主功能已开启"
} else {
bt_open_main.setBackgroundResource(R.drawable.comment_red_btn)
bt_open_main.text = "开启主功能"
}
}
/**
* 打开辅助
*/
private fun openAccessibility() {
val clickListener =
DialogInterface.OnClickListener { dialog, which ->
freshOpenMain()
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
}
val cancel = DialogInterface.OnCancelListener {
freshOpenMain()
}
val cancelListener = DialogInterface.OnClickListener { dialog, which ->
freshOpenMain()
}
val dialog: AlertDialog = AlertDialog.Builder(this)
.setMessage(R.string.tips)
.setOnCancelListener(cancel)
.setNegativeButton("取消", cancelListener)
.setPositiveButton("确定", clickListener)
.create()
dialog.show()
}
private fun updateRobotQaUrl(callbackUrl: String) {
try {
val json = hashMapOf<String, Any>()
json["robotId"] = Constant.robotId
if (callbackUrl.isEmpty()) {
json["openCallback"] = 0
} else {
json["openCallback"] = 1
json["callbackUrl"] = callbackUrl
}
val requestBody = RequestBody.create(
MediaType.parse("application/json;charset=UTF-8"),
GsonUtils.toJson(json)
)
val call = object : StringCallback() {
override fun onSuccess(response: Response<String>?) {
if (response != null && JSONObject(response.body()).getInt("code") == 200) {
Constant.qaUrl = callbackUrl
ToastUtils.showLong(if (callbackUrl.isEmpty()) "关闭成功" else "更新成功")
} else {
onError(response)
}
}
override fun onError(response: Response<String>?) {
ToastUtils.showLong(if (callbackUrl.isEmpty()) "关闭失败,请稍后再试~" else "更新失败,请稍后再试~")
}
}
OkGo.post<String>(Constant.getRobotUpdateUrl()).upRequestBody(requestBody).execute(call)
} catch (e: Exception) {
throw RuntimeException(e)
}
}
private fun updateRobotReplyStrategy(type: Int) {
try {
val json = hashMapOf<String, Any>()
json["robotId"] = Constant.robotId
json["replyAll"] = type
val requestBody = RequestBody.create(
MediaType.parse("application/json;charset=UTF-8"),
GsonUtils.toJson(json)
)
val call = object : StringCallback() {
override fun onSuccess(response: Response<String>?) {
if (response != null && JSONObject(response.body()).getInt("code") == 200) {
Constant.replyStrategy = type
ToastUtils.showLong("更新成功")
} else {
onError(response)
}
}
override fun onError(response: Response<String>?) {
ToastUtils.showLong("更新失败,请稍后再试~")
}
}
OkGo.post<String>(Constant.getRobotUpdateUrl()).upRequestBody(requestBody).execute(call)
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}

View File

@@ -46,6 +46,7 @@ public class WeworkMessageBean {
* 获取群信息 GET_GROUP_INFO
* 获取好友信息 GET_FRIEND_INFO
* 获取我的信息 GET_MY_INFO
* 获取最近聊天列表 GET_RECENT_LIST
*/
public static final int HEART_BEAT = 11;
public static final int TYPE_RECEIVE_MESSAGE_LIST = 101;
@@ -82,6 +83,7 @@ public class WeworkMessageBean {
public static final int GET_FRIEND_INFO = 502;
public static final int GET_MY_INFO = 503;
public static final int GET_GROUP_QRCODE = 504;
public static final int GET_RECENT_LIST = 505;
/**
* roomType
@@ -165,7 +167,7 @@ public class WeworkMessageBean {
//转发附加留言
public String extraText;
//接收消息类型
public int textType;
public Integer textType;
//群名
public String groupName;
@@ -206,7 +208,7 @@ public class WeworkMessageBean {
public WeworkMessageBean() {}
public WeworkMessageBean(String receivedName, String receivedContent, int type, Integer roomType, List<String> titleList, List<SubMessageBean> messageList, String log) {
public WeworkMessageBean(String receivedName, String receivedContent, Integer type, Integer roomType, List<String> titleList, List<SubMessageBean> messageList, String log) {
this.type = type;
this.roomType = roomType;
this.titleList = titleList;
@@ -217,36 +219,62 @@ public class WeworkMessageBean {
}
//消息类型
public int type = 0;
public Integer type = 0;
//消息列表的每条消息
public static class SubMessageBean {
//0其他人 1机器人自己 2unknown(如系统消息)
public int sender = 0;
public Integer sender;
//消息类型判断 仅针对sender=0
public int textType;
public Integer textType;
public List<ItemMessageBean> itemMessageList;
public List<String> nameList;
public SubMessageBean(int sender, int textType, List<ItemMessageBean> itemMessageList, List<String> nameList) {
public SubMessageBean(Integer sender, Integer textType, List<ItemMessageBean> itemMessageList, List<String> nameList) {
this.sender = sender;
this.textType = textType;
this.itemMessageList = itemMessageList;
this.nameList = nameList;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SubMessageBean that = (SubMessageBean) o;
return Objects.equals(sender, that.sender) && Objects.equals(textType, that.textType) && Objects.equals(itemMessageList, that.itemMessageList) && Objects.equals(nameList, that.nameList);
}
@Override
public int hashCode() {
return Objects.hash(sender, textType, itemMessageList, nameList);
}
}
//消息列表每条消息的text推断
public static class ItemMessageBean {
//0消息主体上方信息 如日期等 系统消息(拉人/撤回/外部群等居中的提示语)
//2消息内容
public int feature = 0;
public Integer feature;
public String text;
public ItemMessageBean(int feature, String text) {
public ItemMessageBean(Integer feature, String text) {
this.feature = feature;
this.text = text;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ItemMessageBean that = (ItemMessageBean) o;
return Objects.equals(feature, that.feature) && Objects.equals(text, that.text);
}
@Override
public int hashCode() {
return Objects.hash(feature, text);
}
}
//我的信息
@@ -315,7 +343,7 @@ public class WeworkMessageBean {
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(messageId, that.messageId) && 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(at, that.at) && Objects.equals(atList, that.atList) && 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(groupRemark, that.groupRemark) && Objects.equals(groupTemplate, that.groupTemplate) && 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) && Objects.equals(qrcode, that.qrcode) && Objects.equals(friend, that.friend) && Objects.equals(fileBase64, that.fileBase64) && Objects.equals(fileUrl, that.fileUrl) && Objects.equals(fileType, that.fileType);
return Objects.equals(messageId, that.messageId) && 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(at, that.at) && Objects.equals(atList, that.atList) && Objects.equals(originalContent, that.originalContent) && Objects.equals(nameList, that.nameList) && Objects.equals(extraText, that.extraText) && Objects.equals(textType, that.textType) && 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(groupRemark, that.groupRemark) && Objects.equals(groupTemplate, that.groupTemplate) && Objects.equals(newGroupName, that.newGroupName) && Objects.equals(newGroupAnnouncement, that.newGroupAnnouncement) && Objects.equals(removeList, that.removeList) && Objects.equals(showMessageHistory, that.showMessageHistory) && Objects.equals(myInfo, that.myInfo) && Objects.equals(objectName, that.objectName) && Objects.equals(qrcode, that.qrcode) && Objects.equals(friend, that.friend) && Objects.equals(fileBase64, that.fileBase64) && Objects.equals(fileUrl, that.fileUrl) && Objects.equals(fileType, that.fileType) && Objects.equals(type, that.type);
}
@Override

View File

@@ -144,12 +144,18 @@ fun backPress() {
LogUtils.d("尝试点击确定/我知道了/暂不进入")
AccessibilityUtil.performClick(confirm)
} else {
LogUtils.d("未找到对话框 点击bar中心")
AccessibilityUtil.performXYClick(WeworkController.weworkService, ScreenUtils.getScreenWidth() / 2F, BarUtils.getStatusBarHeight() * 2F)
sleep(Constant.POP_WINDOW_INTERVAL)
val firstEmptyTextView = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView).firstOrNull { it.text.isNullOrEmpty() }
if (firstEmptyTextView != null && firstEmptyTextView.isClickable) {
AccessibilityUtil.performClick(firstEmptyTextView)
val stayButton = AccessibilityUtil.findOnceByText(getRoot(true), "关闭应用", "等待", exact = true)
if (stayButton != null) {
LogUtils.d("疑似ANR 尝试点击等待")
AccessibilityUtil.performClick(stayButton)
} else {
LogUtils.d("未找到对话框 点击bar中心")
AccessibilityUtil.performXYClick(WeworkController.weworkService, ScreenUtils.getScreenWidth() / 2F, BarUtils.getStatusBarHeight() * 2F)
sleep(Constant.POP_WINDOW_INTERVAL)
val firstEmptyTextView = AccessibilityUtil.findAllByClazz(getRoot(), Views.TextView).firstOrNull { it.text.isNullOrEmpty() }
if (firstEmptyTextView != null && firstEmptyTextView.isClickable) {
AccessibilityUtil.performClick(firstEmptyTextView)
}
}
}
}

View File

@@ -6,12 +6,14 @@ import android.os.Message
import com.blankj.utilcode.util.EncryptUtils
import com.blankj.utilcode.util.GsonUtils
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.SPUtils
import com.google.gson.reflect.TypeToken
import okhttp3.WebSocket
import org.yameida.worktool.Constant
import org.yameida.worktool.model.ExecCallbackBean
import org.yameida.worktool.model.WeworkMessageBean
import org.yameida.worktool.model.WeworkMessageListBean
import org.yameida.worktool.utils.FloatWindowHelper
import java.nio.charset.StandardCharsets
import java.util.LinkedHashSet
import kotlin.concurrent.thread
@@ -21,12 +23,16 @@ object MyLooper {
private var threadHandler: Handler? = null
val looper = thread {
LogUtils.e("myLooper starting...")
LogUtils.i("myLooper starting...")
Looper.prepare()
val myLooper = Looper.myLooper()
if (myLooper != null) {
threadHandler = object : Handler(myLooper) {
override fun handleMessage(msg: Message) {
while (FloatWindowHelper.isPause) {
LogUtils.i("主功能暂停...")
sleep(Constant.CHANGE_PAGE_INTERVAL)
}
LogUtils.d("handle message: " + Thread.currentThread().name, msg)
try {
dealWithMessage(msg.obj as WeworkMessageBean)
@@ -42,7 +48,11 @@ object MyLooper {
Looper.loop()
}
fun init() {}
fun init() {
LogUtils.i("init myLooper...")
SPUtils.getInstance("noTipMessage").clear()
SPUtils.getInstance("limit").clear()
}
fun getInstance(): Handler {
while (true) {
@@ -168,6 +178,9 @@ object MyLooper {
WeworkMessageBean.GET_MY_INFO -> {
WeworkController.getMyInfo(message)
}
WeworkMessageBean.GET_RECENT_LIST -> {
WeworkController.getRecentList(message)
}
WeworkMessageBean.ROBOT_CONTROLLER_TEST -> {
WeworkController.test(message)
}

View File

@@ -390,4 +390,14 @@ object WeworkController {
return WeworkGetImpl.getMyInfo(message)
}
/**
* 获取最近聊天列表
* @see WeworkMessageBean.GET_RECENT_LIST
*/
@RequestMapping
fun getRecentList(message: WeworkMessageBean): Boolean {
LogUtils.d("getRecentList():")
return WeworkGetImpl.getRecentList(message)
}
}

View File

@@ -197,4 +197,45 @@ object WeworkGetImpl {
backPress()
return weworkMessageBean
}
/**
* 获取最近聊天列表
*/
fun getRecentList(message: WeworkMessageBean): Boolean {
goHome()
AccessibilityUtil.scrollToTop(WeworkController.weworkService, getRoot())
val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup)
if (list != null && list.childCount >= 2) {
val listBriefList = LinkedHashSet<WeworkMessageBean.SubMessageBean>()
val onScrollListener = object : AccessibilityUtil.OnScrollListener() {
override fun onScroll(): Boolean {
list.refresh()
for (i in 0 until list.childCount) {
val item = list.getChild(i)
val tempList = arrayListOf<WeworkMessageBean.ItemMessageBean>()
val tvList = AccessibilityUtil.findAllOnceByClazz(item, Views.TextView).mapNotNull { it.text }
tvList.forEach { tempList.add(WeworkMessageBean.ItemMessageBean(null, it.toString())) }
listBriefList.add(WeworkMessageBean.SubMessageBean(null, null, tempList, null))
//tvList title/time/content
if (tvList.size == 3) {
//只查看最近一周内的消息
if (tvList[1].isNotBlank() && !tvList[1].contains("(刚刚)|(分钟前)|(上午)|(下午)|(昨天)|(星期)|(日程)|(会议)".toRegex())) {
return true
}
}
}
return false
}
}
//滚动前先获取一次
onScrollListener.onScroll()
AccessibilityUtil.scrollToBottom(WeworkController.weworkService, getRoot(), listener = onScrollListener)
LogUtils.d("最近聊天列表", GsonUtils.toJson(listBriefList))
val weworkMessageBean = WeworkMessageBean()
weworkMessageBean.type = WeworkMessageBean.GET_RECENT_LIST
weworkMessageBean.messageList = listBriefList.toList()
WeworkController.weworkService.webSocketManager.send(weworkMessageBean)
}
return true
}
}

View File

@@ -3,8 +3,8 @@ package org.yameida.worktool.service
import android.view.accessibility.AccessibilityNodeInfo
import androidx.core.text.isDigitsOnly
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.SPUtils
import org.yameida.worktool.Constant
import org.yameida.worktool.Demo
import org.yameida.worktool.model.WeworkMessageBean
import org.yameida.worktool.service.WeworkController.mainLoopRunning
import org.yameida.worktool.utils.*
@@ -141,9 +141,7 @@ object WeworkLoopImpl {
if (lastMessage != null) {
var tempContent = ""
for (itemMessage in lastMessage.itemMessageList) {
if (itemMessage.text.contains("@" + Constant.myName)
|| itemMessage.text.isDigitsOnly()
) {
if (itemMessage.text.contains("@" + Constant.myName)) {
tempContent = itemMessage.text
}
}
@@ -235,8 +233,24 @@ object WeworkLoopImpl {
}
}
if (logIndex % 120 == 0) {
//让企微切换页面使APP保持活跃
goHomeTab("通讯录")
goHomeTab("消息")
//滚动到顶端查看是否有无提示消息
AccessibilityUtil.scrollToTop(WeworkController.weworkService, getRoot())
val listview = AccessibilityUtil.findOneByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup)
if (listview != null && listview.childCount >= 2) {
if (checkNoTipMessage(listview) != 1) {
AccessibilityUtil.scrollToBottom(WeworkController.weworkService, getRoot(), listener = object : AccessibilityUtil.OnScrollListener() {
override fun onScroll(): Boolean {
if (checkNoTipMessage(listview) != 0) {
return true
}
return false
}
})
}
}
}
if (!isAtHome()) return true
if (logIndex++ % 30 == 0) {
@@ -244,15 +258,15 @@ object WeworkLoopImpl {
if (logIndex % 120 == 0) log("读取首页聊天列表")
}
val listview = AccessibilityUtil.findOneByClazz(getRoot(), Views.RecyclerView, Views.ListView, Views.ViewGroup)
if (listview != null) {
if (listview.childCount >= 2) {
if (checkUnreadChatRoom(listview)) {
//进入聊天页
return true
}
if (listview != null && listview.childCount >= 2) {
if (checkUnreadChatRoom(listview)) {
//如果有红点 点击进入聊天页
return true
} else if (checkNoTipMessage(listview) == 1) {
//如果发现拉入群聊/修改群名/移出群聊 点击进入聊天页
return true
} else {
LogUtils.e("读取聊天列表失败")
error("读取聊天列表失败")
LogUtils.v("未发现新消息或无提示消息")
}
} else {
LogUtils.e("读取聊天列表失败")
@@ -263,7 +277,6 @@ object WeworkLoopImpl {
/**
* 检查首页-聊天列表是否有未读红点并点击进入
* 获取红点
*/
private fun checkUnreadChatRoom(list: AccessibilityNodeInfo): Boolean {
val spotNodeList = arrayListOf<AccessibilityNodeInfo>()
@@ -296,6 +309,45 @@ object WeworkLoopImpl {
}
}
/**
* 检查首页-聊天列表是否有拉入群聊/修改群名/移出群聊等无提示消息
* @return -1当前列表不存在一周内消息 0未发现无提示消息 1发现无提示消息
*/
private fun checkNoTipMessage(list: AccessibilityNodeInfo): Int {
list.refresh()
val listBriefList = arrayListOf<List<CharSequence>>()
for (i in 0 until list.childCount) {
val item = list.getChild(i)
val tvList = AccessibilityUtil.findAllOnceByClazz(item, Views.TextView).mapNotNull { it.text }
listBriefList.add(tvList)
//tvList title/time/content
if (tvList.size == 3) {
//只查看最近一周内的消息
if (tvList[1].isBlank() || tvList[1].contains("(刚刚)|(分钟前)|(上午)|(下午)|(昨天)|(星期)|(日程)|(会议)".toRegex())) {
if (tvList[2].contains("(移出了群聊)|(邀请你加入了)|(修改群名为)|(此群为外部群)|(加入了外部群)".toRegex())) {
val interval = System.currentTimeMillis() / 1000 - SPUtils.getInstance("noTipMessage").getLong(tvList[0].toString(), 0)
if (interval > 3600) {
LogUtils.i("发现无提示消息: $tvList")
log("发现无提示消息: $tvList")
if (AccessibilityUtil.performClick(item)) {
//进入聊天页 下一步 getChatMessageList
} else {
AccessibilityUtil.clickByNode(WeworkController.weworkService, item)
}
SPUtils.getInstance("noTipMessage").put(tvList[0].toString(), System.currentTimeMillis() / 1000)
return 1
} else {
LogUtils.v("发现无提示消息: $tvList 消息在 $interval 秒前已被查看")
}
}
} else {
return -1
}
}
}
return 0
}
/**
* 解析消息列表里的一条消息
*/

View File

@@ -215,6 +215,10 @@ object WeworkOperationImpl {
): Boolean {
val startTime = System.currentTimeMillis()
if (!WeworkRoomUtil.isGroupExists(groupName)) {
if (!beforeCreateGroupCheck()) {
uploadCommandResult(message, ExecCallbackBean.ERROR_CREATE_GROUP_LIMIT, "建群达到上限", startTime)
return false
}
if (!createGroup()) {
uploadCommandResult(message, ExecCallbackBean.ERROR_CREATE_GROUP, "创建群失败", startTime)
return false
@@ -720,9 +724,13 @@ object WeworkOperationImpl {
Views.ImageView
)
)
} else if (AccessibilityUtil.findOnceByText(getRoot(), "该用户不存在") != null) {
LogUtils.e("该用户不存在: ${friend.phone}")
uploadCommandResult(message, ExecCallbackBean.ERROR_TARGET, "该用户不存在: ${friend.phone}", startTime)
return false
}
if (modifyFriendInfo(friend)) {
if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) {
if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人", timeout = 2000)) {
LogUtils.d("准备发送好友邀请: ${friend.phone}")
if (!friend.leavingMsg.isNullOrEmpty()) {
AccessibilityUtil.findTextInput(getRoot(), friend.leavingMsg)
@@ -833,7 +841,7 @@ object WeworkOperationImpl {
}
}
if (modifyFriendInfo(friend)) {
if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) {
if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人", timeout = 2000)) {
LogUtils.d("准备发送好友邀请: ${friend.name}")
if (!friend.leavingMsg.isNullOrEmpty()) {
AccessibilityUtil.findTextInput(getRoot(), friend.leavingMsg)
@@ -906,6 +914,9 @@ object WeworkOperationImpl {
): Boolean {
val startTime = System.currentTimeMillis()
goHome()
if (AccessibilityUtil.findOnceByText(getRoot(), "日程", exact = true) == null) {
AccessibilityUtil.scrollToTop(WeworkController.weworkService, getRoot())
}
val tvDiaryFlag = AccessibilityUtil.findOneByText(getRoot(), "日程", exact = true)
if (tvDiaryFlag != null && (tvDiaryFlag.parent?.childCount == 2 || tvDiaryFlag.parent?.childCount == 3)) {
AccessibilityUtil.performClick(tvDiaryFlag)
@@ -1141,11 +1152,34 @@ object WeworkOperationImpl {
}
/**
* 检查是否达到当日建群上限
* 建群前检查是否达到当日建群上限
* @return true允许建群 false不允许建群
*/
private fun beforeCreateGroupCheck(): Boolean {
//有建群权限且最近300秒内发现限制建群
if (SPUtils.getInstance("limit").getBoolean("canCreateGroup", false)) {
val interval = System.currentTimeMillis() / 1000 - SPUtils.getInstance("limit").getLong("createGroupLimit", 0)
if (interval < 300) {
LogUtils.e("发现达到当日建群上限 请等待${300 - interval}秒后再试!")
return false
}
}
return true
}
/**
* 建群后检查是否达到当日建群上限
* @return true达到上限 false为达到上限
*/
private fun createGroupLimit(): Boolean {
val hasLimit =
AccessibilityUtil.findOneByText(getRoot(), "新建群聊功能暂时被限制", "未验证企业", timeout = 2000)
if (hasLimit == null) {
SPUtils.getInstance("limit").put("canCreateGroup", true)
} else if (SPUtils.getInstance("limit").getBoolean("canCreateGroup", false)) {
SPUtils.getInstance("limit").put("createGroupLimit", System.currentTimeMillis() / 1000)
LogUtils.e("发现达到当日建群上限")
}
return hasLimit != null
}

View File

@@ -15,6 +15,7 @@ import org.yameida.worktool.Constant
import org.yameida.worktool.Demo
import org.yameida.worktool.utils.*
import java.lang.Exception
import kotlin.concurrent.thread
/**
* 企业微信辅助服务
@@ -24,6 +25,8 @@ import java.lang.Exception
class WeworkService : AccessibilityService() {
private val TAG = "WeworkService"
lateinit var webSocketManager: WebSocketManager
var currentPackage = ""
var currentClass = ""
override fun onServiceConnected() {
LogUtils.i("初始化成功")
@@ -35,7 +38,7 @@ class WeworkService : AccessibilityService() {
//初始化消息处理器
MyLooper.init()
//开发者可以在这里添加测试代码 启动时调用一次
Demo.test(AppUtils.isAppDebug())
thread { Demo.test(AppUtils.isAppDebug()) }
//监听是否修改链接号并重新长连接
registerReceiver(object : BroadcastReceiver() {
@@ -62,6 +65,11 @@ class WeworkService : AccessibilityService() {
* @param event
*/
override fun onAccessibilityEvent(event: AccessibilityEvent) {
currentPackage = event.packageName?.toString() ?: ""
val className = event.className?.toString() ?: ""
if (className.contains(currentPackage)) {
currentClass = className
}
}
override fun onInterrupt() {
@@ -83,13 +91,13 @@ class WeworkService : AccessibilityService() {
private lateinit var socket: WebSocket
override fun onOpen(webSocket: WebSocket, response: Response) {
socket = webSocket
Log.e(TAG, "接建立")
val robotId = SPUtils.getInstance().getString(Constant.LISTEN_CHANNEL_ID, "")
Log.e(TAG, "接建立")
val robotId = Constant.robotId
val appVersion = SPUtils.getInstance().getString("appVersion", "")
val workVersion = SPUtils.getInstance().getString("workVersion", "")
val deviceRooted = SPUtils.getInstance().getBoolean("deviceRooted", false)
val hook = SPUtils.getInstance().getBoolean("hook", false)
log("接建立: $robotId appVersion: $appVersion workVersion: $workVersion deviceRooted: $deviceRooted hook: $hook")
log("接建立: $robotId appVersion: $appVersion workVersion: $workVersion deviceRooted: $deviceRooted hook: $hook")
LogUtils.i("设置自动跳转企业微信")
sendBroadcast(true)
}
@@ -107,7 +115,7 @@ class WeworkService : AccessibilityService() {
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
super.onClosed(webSocket, code, reason)
//服务器关闭后
Log.e(TAG, "接关闭 $reason")
Log.e(TAG, "接关闭 $reason")
sendBroadcast(false)
}
@@ -120,7 +128,7 @@ class WeworkService : AccessibilityService() {
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
//服务器中断
Log.e(TAG, "接错误: " + t.toString() + response.toString())
Log.e(TAG, "接错误: " + t.toString() + response.toString())
sendBroadcast(false)
}

View File

@@ -22,6 +22,7 @@ import org.yameida.worktool.service.WeworkController
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import java.lang.StringBuilder
/**
@@ -54,11 +55,21 @@ import android.content.Context
* performLongClick 对某个节点或父节点进行长按
* performLongClickWithSon 对某个节点或子节点进行长按
*
* 注意:操作均为阻塞式,原则上本工具类所有操作都应在子线程执行
*/
object AccessibilityUtil {
private const val tag = "AccessibilityUtil"
private const val SHORT_INTERVAL = 150L
private const val SCROLL_INTERVAL = 500L
private const val SCROLL_INTERVAL_NATIVE = 500L
private const val SCROLL_INTERVAL = 800L
/**
* 滚动监听
* 如果期望停止滚动则在onScroll回调中返回true 否则返回false
*/
abstract class OnScrollListener {
abstract fun onScroll(): Boolean
}
//编辑EditView(粘贴 不推荐)
fun sendTextForEditText(context: Context, nodeInfo: AccessibilityNodeInfo?, text: String): Boolean {
@@ -84,8 +95,13 @@ object AccessibilityUtil {
}
//寻找第一个文本匹配(关键词)并点击
fun findTextAndClick(nodeInfo: AccessibilityNodeInfo?, vararg textList: String): Boolean {
val textView = findOneByText(nodeInfo, *textList) ?: return false
fun findTextAndClick(nodeInfo: AccessibilityNodeInfo?,
vararg textList: String,
exact: Boolean = false,
timeout: Long = 5000,
root: Boolean = true
): Boolean {
val textView = findOneByText(nodeInfo, *textList, exact = exact, timeout = timeout, root = root) ?: return false
return performClick(textView)
}
@@ -117,6 +133,106 @@ object AccessibilityUtil {
return false
}
//滚动到顶部
fun scrollToTop(
service: AccessibilityService,
nodeInfo: AccessibilityNodeInfo,
scrollNodeIndex: Int = 0,
tryUseGesture: Boolean = true,
listener: OnScrollListener? = null,
maxRetry: Int = 10
): Boolean {
var textChanged = false
var index = 0
while (index++ < maxRetry) {
val scrollBefore = findAllOnceByClazz(getRoot(), Views.TextView)
performScrollUp(nodeInfo, scrollNodeIndex)
if (scrollBefore == findAllOnceByClazz(getRoot(), Views.TextView)) {
LogUtils.d("已经滚动到顶部")
if (textChanged) {
return true
} else {
break
}
} else {
textChanged = true
LogUtils.v("未滚动到顶部 $index")
}
}
if (tryUseGesture) {
LogUtils.d("未找到可滚动列表 使用手势滚动")
val width = ScreenUtils.getScreenWidth()
val height = ScreenUtils.getScreenHeight()
index = 0
while (index++ < maxRetry) {
val scrollBefore = findAllOnceByClazz(getRoot(), Views.TextView)
scrollByXY(service, width / 2, (height / 2.2).toInt(), 0, height / 3)
if (scrollBefore == findAllOnceByClazz(getRoot(), Views.TextView)) {
LogUtils.d("已经滚动到顶部")
break
} else {
LogUtils.v("未滚动到顶部 $index")
if (listener != null && listener.onScroll()) {
LogUtils.d("提前终止滚动")
return true
}
}
}
return true
}
return false
}
//滚动到顶部
fun scrollToBottom(
service: AccessibilityService,
nodeInfo: AccessibilityNodeInfo,
scrollNodeIndex: Int = 0,
tryUseGesture: Boolean = true,
listener: OnScrollListener? = null,
maxRetry: Int = 10
): Boolean {
var textChanged = false
var index = 0
while (index++ < maxRetry) {
val scrollBefore = findAllOnceByClazz(getRoot(), Views.TextView)
performScrollDown(nodeInfo, scrollNodeIndex)
if (scrollBefore == findAllOnceByClazz(getRoot(), Views.TextView)) {
LogUtils.d("已经滚动到底部")
if (textChanged) {
return true
} else {
break
}
} else {
textChanged = true
LogUtils.v("未滚动到底部 $index")
if (listener != null && listener.onScroll()) {
LogUtils.d("提前终止滚动")
return true
}
}
}
if (tryUseGesture) {
LogUtils.d("未找到可滚动列表 使用手势滚动")
val width = ScreenUtils.getScreenWidth()
val height = ScreenUtils.getScreenHeight()
index = 0
while (index++ < maxRetry) {
val scrollBefore = findAllOnceByClazz(getRoot(), Views.TextView)
scrollByXY(service, width / 2, (height / 2.2).toInt(), 0, -height / 3)
if (scrollBefore == findAllOnceByClazz(getRoot(), Views.TextView)) {
LogUtils.d("已经滚动到底部")
break
} else {
LogUtils.v("未滚动到底部 $index")
}
}
return true
}
return false
}
//滚动并按文本寻找第一个控件
fun scrollAndFindByText(
service: AccessibilityService,
@@ -124,6 +240,10 @@ object AccessibilityUtil {
vararg textList: String,
maxRetry: Int = 3
): AccessibilityNodeInfo? {
val node = findOnceByText(nodeInfo, *textList)
if (node != null) {
return node
}
var index = 0
while (index++ < maxRetry) {
performScrollDown(nodeInfo, 0)
@@ -146,8 +266,7 @@ object AccessibilityUtil {
val height = ScreenUtils.getScreenHeight()
index = 0
while (index++ < maxRetry * 2) {
scrollByXY(service, width / 2, height / 2, 0, -height / 2)
sleep(SCROLL_INTERVAL)
scrollByXY(service, width / 2, (height / 2.2).toInt(), 0, -height / 3)
val node = findOnceByText(nodeInfo, *textList)
if (node != null) {
return node
@@ -155,8 +274,7 @@ object AccessibilityUtil {
}
index = 0
while (index++ < maxRetry * 3) {
scrollByXY(service, width / 2, height / 2, 0, height / 2)
sleep(SCROLL_INTERVAL)
scrollByXY(service, width / 2, (height / 2.2).toInt(), 0, height / 3)
val node = findOnceByText(nodeInfo, *textList)
if (node != null) {
return node
@@ -299,7 +417,7 @@ object AccessibilityUtil {
val canScrollNodeList = findCanScrollNode(node)
if (canScrollNodeList.size > index) {
canScrollNodeList[index].performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD)
sleep(SCROLL_INTERVAL)
sleep(SCROLL_INTERVAL_NATIVE)
return true
}
return false
@@ -311,7 +429,7 @@ object AccessibilityUtil {
val canScrollNodeList = findCanScrollNode(node)
if (canScrollNodeList.size > index) {
canScrollNodeList[index].performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
sleep(SCROLL_INTERVAL)
sleep(SCROLL_INTERVAL_NATIVE)
return true
}
return false
@@ -814,22 +932,31 @@ object AccessibilityUtil {
node: AccessibilityNodeInfo?,
printText: Boolean = true,
depth: Int = 0
) {
if (node == null) return
): StringBuilder {
val sb = StringBuilder()
if (node == null) return sb
var s = ""
for (i in 0 until depth) {
s += "---"
}
Log.d(tag, "$s depth: $depth className: " + node.className + " isClickable: " + node.isClickable)
val temp = "$s depth: $depth className: " + node.className + " isClickable: " + node.isClickable
Log.d(tag, temp)
sb.append(temp).append("\n")
var text = ""
if (printText && node.text != null) {
Log.d(tag, "$s depth: $depth text: " + node.text)
text = "$s depth: $depth text: " + node.text
Log.d(tag, text)
sb.append(text).append("\n")
}
if (printText && node.contentDescription != null) {
Log.d(tag, "$s depth: $depth desc: " + node.contentDescription)
val desc = "$s depth: $depth desc: " + node.contentDescription
Log.d(tag, desc)
sb.append(desc).append("\n")
}
for (i in 0 until node.childCount) {
printNodeClazzTree(node.getChild(i), printText, depth + 1)
sb.append(printNodeClazzTree(node.getChild(i), printText, depth + 1))
}
return sb
}
/**
@@ -946,7 +1073,7 @@ object AccessibilityUtil {
path.lineTo(x.toFloat() + distanceX, y.toFloat() + distanceY)
builder.addStroke(StrokeDescription(path, 0L, 300L))
val gesture = builder.build()
return service.dispatchGesture(gesture, object : GestureResultCallback() {
val dispatchGesture = service.dispatchGesture(gesture, object : GestureResultCallback() {
override fun onCompleted(gestureDescription: GestureDescription) {
LogUtils.v("scroll ok onCompleted")
}
@@ -955,5 +1082,7 @@ object AccessibilityUtil {
LogUtils.v("scroll ok onCancelled")
}
}, null)
sleep(SCROLL_INTERVAL)
return dispatchGesture
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,74 @@
package org.yameida.worktool.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.widget.Toast
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.Utils
import com.tencent.wework.api.IWWAPI
import com.tencent.wework.api.WWAPIFactory
import com.tencent.wework.api.model.WWMediaLink
import com.tencent.wework.api.model.WWMediaMiniProgram
import com.tencent.wework.api.model.WWSimpleRespMessage
object IWWAPIUtil {
private var iwwapi: IWWAPI? = null
var appid = "wwe51e5ed82702b49b" //企业唯一标识。创建企业后显示在,我的企业 CorpID字段
var agentid = "1000002" //应用唯一标识。显示在具体应用下的 AgentId字段
var schema = "wwauthe51e5ed82702b49b000002"
fun init(context: Context, schema: String = this.schema) {
this.schema = schema
iwwapi = WWAPIFactory.createWWAPI(context)
iwwapi?.registerApp(schema)
}
fun sendLink(thumbUrl: String?, webpageUrl: String?, title: String?, description: String?) {
val link = WWMediaLink()
link.thumbUrl = thumbUrl
link.webpageUrl = webpageUrl
link.title = title
link.description = description
link.appPkg = AppUtils.getAppPackageName()
link.appName = AppUtils.getAppName()
link.appId = appid
link.agentId = agentid
iwwapi?.sendMessage(link)
}
fun sendMicroProgram() {
val miniProgram = WWMediaMiniProgram()
miniProgram.appPkg = AppUtils.getAppPackageName()
miniProgram.appName = AppUtils.getAppName()
miniProgram.appId = appid
miniProgram.agentId = agentid
miniProgram.schema = schema
miniProgram.username = "gh_dde54cb88ce7@app" //必须是应用关联的小程序,注意要有@app后缀
miniProgram.description = "dddddd"
miniProgram.path = "/pages/plugin/index.html?plugid=1cbd3b7c8674e61769436b5e354ddb2f"
// val bitmap = (getDrawable(R.drawable.test) as BitmapDrawable).bitmap
// val stream = ByteArrayOutputStream()
// bitmap.compress(Bitmap.CompressFormat.JPEG, 0, stream)
// val byteArray: ByteArray = stream.toByteArray()
// miniProgram.hdImageData = byteArray
miniProgram.title = "测试_MaHow"
iwwapi!!.sendMessage(miniProgram) { resp ->
if (resp is WWSimpleRespMessage) {
val rsp = resp as WWSimpleRespMessage
var t: String? = ""
Toast.makeText(
Utils.getApp(),
"发小程序," + rsp.errCode + "," + rsp.errMsg.also {
t = it
},
Toast.LENGTH_LONG
).show()
}
}
}
}

View File

@@ -17,7 +17,6 @@ object PermissionHelper {
val canonicalName = WeworkService::class.java.canonicalName ?: ""
val serviceName = context.packageName + "/" + canonicalName
val serviceShortName = context.packageName + "/" + canonicalName.replace(context.packageName, "")
LogUtils.i("isAccessibilitySettingOn: $serviceName $serviceShortName")
try {
enable = Settings.Secure.getInt(
context.contentResolver,
@@ -27,6 +26,7 @@ object PermissionHelper {
} catch (e: Exception) {
e.printStackTrace()
}
var flag = false
if (enable == 1) {
val stringSplitter = TextUtils.SimpleStringSplitter(':')
val settingVal = Settings.Secure.getString(
@@ -38,14 +38,14 @@ object PermissionHelper {
while (stringSplitter.hasNext()) {
val accessibilityService = stringSplitter.next()
if (accessibilityService == serviceName || accessibilityService == serviceShortName) {
LogUtils.i("isAccessibilitySettingOn: true")
return true
flag = true
break
}
}
}
}
LogUtils.i("isAccessibilitySettingOn: false")
return false
LogUtils.v("isAccessibilitySettingOn: $serviceName $serviceShortName $flag")
return flag
}
}

View File

@@ -15,9 +15,10 @@ object RegexHelper {
.replace("{", "\\{")
.replace("}", "\\}")
.replace("|", "\\|")
// .replace("-", "\\-") //企微自身限制
// .replace("(", "\\(") //企微自身限制
// .replace(")", "\\)") //企微自身限制
//企微自身存在限制
.replace("-", "\\-")
.replace("(", "\\(")
.replace(")", "\\)")
}
}

View File

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

View File

@@ -37,6 +37,7 @@ public class WebSocketManager {
private String url;
private WebSocketListener listener;
private boolean connecting = false;
private long lastConnectedTime = 0L;
public WebSocketManager(String url, WebSocketListener listener) {
Log.e(url, "新建链接");
@@ -113,18 +114,25 @@ public class WebSocketManager {
Log.e(url, "重连");
boolean isConnect = false;
int interval = reconnectInt;
while (!isConnect) {
while (true) {
try {
isConnect = connect();
Thread.sleep(interval);
if (interval < 600000) {
interval *= 2;
if (isConnect) {
connecting = false;
break;
}
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(interval);
if (interval < 600000) {
interval *= 2;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
connecting = false;
}
private boolean connect() {
@@ -137,6 +145,7 @@ public class WebSocketManager {
}
private ScheduledFuture heartCheckStart() {
lastConnectedTime = System.currentTimeMillis();
Runnable r = () -> {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
Log.e(url, "心跳检测" + df.format(new Date()));// new Date()为获取当前系统时间
@@ -145,8 +154,12 @@ public class WebSocketManager {
WeworkController.INSTANCE.setEnableLoopRunning(false);
//断开链接后进入重连
reConnect();
//重连后刷新连接时间
lastConnectedTime = System.currentTimeMillis();
}
if (System.currentTimeMillis() - lastConnectedTime > heartBeatRate * 2000 && !FloatWindowHelper.INSTANCE.isPause()) {
ToastUtils.show("机器人运行中 请勿人工操作手机~");
}
// ToastUtils.show("机器人运行中 请勿人工操作手机~");
};
//每heartBeatRate秒发一次心跳包

View File

@@ -19,7 +19,7 @@ object WeworkRoomUtil {
* @see WeworkMessageBean.ROOM_TYPE
*/
fun getRoomType(print: Boolean = true): Int {
val roomTitle = getRoomTitle(noCut = true)
val roomTitle = getRoomTitle(noCut = true, print = false)
when {
isExternalSingleChat(roomTitle) -> {
LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_CONTACT")

View File

@@ -104,7 +104,7 @@ public class CheckRoot {
fullResponse.add(line);
}
} catch (Exception e) {
e.printStackTrace();
Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
}
Log.i(LOG_TAG, "> Full response was: " + fullResponse);
return fullResponse;
@@ -137,7 +137,7 @@ public class CheckRoot {
}
process.destroy();
} catch (Exception e) {
e.printStackTrace();
Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
}
}
}
@@ -181,8 +181,7 @@ public class CheckRoot {
return false;
}
} catch (Exception e) {
Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
+ e.getMessage());
Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
return false;
}
}
@@ -195,7 +194,7 @@ public class CheckRoot {
fout.close();
return true;
} catch (Exception e) {
e.printStackTrace();
Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
return false;
}
}
@@ -214,7 +213,7 @@ public class CheckRoot {
Log.i(LOG_TAG, result);
return result;
} catch (Exception e) {
e.printStackTrace();
Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage());
return null;
}
}

View File

@@ -0,0 +1,18 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true" >
<com.qmuiteam.qmui.widget.webview.QMUIWebView
android:id="@+id/qmwv"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</com.qmuiteam.qmui.widget.webview.QMUIWebView>
</RelativeLayout>

View File

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

View File

@@ -5,9 +5,46 @@
android:layout_height="match_parent"
android:background="@color/background">
<RelativeLayout
android:id="@+id/rl_bar"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/color_dashen"
android:text="@string/app_name"
/>
<ImageView
android:id="@+id/iv_settings"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:src="@drawable/tab_settings_check" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/list_divider_line"
android:layout_alignParentBottom="true"
/>
</RelativeLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:layout_below="@id/rl_bar">
<LinearLayout
android:layout_width="match_parent"
@@ -231,76 +268,6 @@
</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_start_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<Switch
android:id="@+id/sw_auto_reply"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/sw_auto_reply"
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"
@@ -384,6 +351,9 @@
android:background="@drawable/comment_red_btn"
android:paddingStart="50dp"
android:paddingEnd="50dp"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/white"
android:text="保存" />

View File

@@ -0,0 +1,612 @@
<?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">
<RelativeLayout
android:id="@+id/rl_bar"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/color_dashen"
android:text="@string/app_name"
/>
<ImageView
android:id="@+id/iv_back_left"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:src="@drawable/back_icon" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/list_divider_line"
android:layout_alignParentBottom="true"
/>
</RelativeLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/rl_bar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background"
android:orientation="vertical">
<!-- 视频 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_start_padding"
android:paddingBottom="@dimen/setting_vertical_padding"
android:text="配置"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<RelativeLayout
android:id="@+id/rl_reply_strategy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_rec_reply_strategy_"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:src="@drawable/settings_hq" />
<TextView
android:id="@+id/tv_select_reply_strategy"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/tv_select_reply_strategy"
android:layout_toEndOf="@id/iv_rec_reply_strategy_"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="回复策略"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="设置新消息回复的策略"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_qa_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_qa_url_"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:src="@drawable/settings_fps" />
<TextView
android:id="@+id/tv_select_fps"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/tv_select_fps"
android:layout_toEndOf="@id/iv_qa_url_"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="消息回调"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="设置回调地址来接收和处理新消息"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_rec_orientation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_rec_orientation_"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:src="@drawable/settings_orientation" />
<TextView
android:id="@+id/tv_select_orientation"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/tv_select_orientation"
android:layout_toEndOf="@id/iv_rec_orientation_"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="方向"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_rec_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_rec_location_"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:src="@drawable/settings_directory" />
<TextView
android:id="@+id/tv_select_location"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/tv_select_location"
android:layout_toEndOf="@id/iv_rec_location_"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="save_location"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:id="@+id/tv_save_location_tip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="video_default_path"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<!-- 控制台 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_start_padding"
android:paddingBottom="@dimen/setting_vertical_padding"
android:text="控制台"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<RelativeLayout
android:id="@+id/rl_encrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_start_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_encrypt"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:src="@drawable/settings_no_pop" />
<Switch
android:id="@+id/sw_encrypt"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/sw_encrypt"
android:layout_toEndOf="@id/iv_encrypt"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开启通讯加密"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="阻止网络抓包截取数据"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_rec_resolution"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_start_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_rec_resolution_"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:src="@drawable/settings_hd" />
<Switch
android:id="@+id/sw_receive"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding" />
<TextView
android:id="@+id/tv_select_resolution"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/tv_select_resolution"
android:layout_toEndOf="@id/iv_rec_resolution_"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开启新消息接收"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查看会话列表的所有未读消息"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<!-- 其他 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_start_padding"
android:paddingBottom="@dimen/setting_vertical_padding"
android:text="其他"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<RelativeLayout
android:id="@+id/rl_language"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_rec_language_"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:src="@drawable/settings_language" />
<TextView
android:id="@+id/tv_select_language"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/tv_select_language"
android:layout_toEndOf="@id/iv_rec_language_"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="language"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="reply_strategy_tips"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size"
android:visibility="gone" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_donate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_rec_donate_"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:src="@drawable/settings_rate_us" />
<TextView
android:id="@+id/tv_select_donate"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/tv_select_donate"
android:layout_toEndOf="@id/iv_rec_donate_"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="赞助我们"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="还有机会成为我们的内测用户体验新功能"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_share"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/setting_start_padding"
android:paddingTop="@dimen/setting_vertical_padding"
android:paddingEnd="@dimen/setting_end_padding"
android:paddingBottom="@dimen/setting_vertical_padding">
<ImageView
android:id="@+id/iv_rec_share_"
android:layout_width="@dimen/setting_start_image_width"
android:layout_height="@dimen/setting_start_image_width"
android:layout_centerVertical="true"
android:scaleX="1.1"
android:scaleY="1.1"
android:src="@drawable/settings_share" />
<TextView
android:id="@+id/tv_select_share"
android:layout_width="@dimen/setting_end_font_width"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_end_start_padding"
android:textColor="@color/float_time_color"
android:textSize="@dimen/setting_end_font_size"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/setting_start_padding"
android:layout_toStartOf="@id/tv_select_share"
android:layout_toEndOf="@id/iv_rec_share_"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="分享应用"
android:textColor="@color/color_333333"
android:textSize="@dimen/setting_start_font_size" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="把本应用分享给其他人"
android:textColor="@color/color_999999"
android:textSize="@dimen/setting_end_font_size" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
android:orientation="vertical">
<Button
android:id="@+id/bt_open_main"
style="@style/rec_top_btn"
android:background="@drawable/comment_red_btn"
android:text="开启主功能"
/>
<Button
android:id="@+id/bt_open_flow"
style="@style/rec_top_btn"
android:background="@drawable/comment_red_btn"
android:text="开启悬浮窗" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</RelativeLayout>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#fff"
android:orientation="vertical">
<EditText
android:id="@+id/body"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:hint="wss://"
android:lines="3"
android:textColor="#000000" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#169BD5"
android:text="确定" />
<Button
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#F4F4F4"
android:text="@android:string/cancel" />
</LinearLayout>
</LinearLayout>

View File

@@ -7,17 +7,8 @@
<!-- 升级对话框 -->
<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>
<!-- UI -->
<string name="tip">提示</string>

View File

@@ -21,4 +21,44 @@
<item name="android:textColorHint">@color/textInputLayout</item>
</style>
<style name="ds_rec_btn">
<item name="android:textSize">
@dimen/size_button
</item>
<item name="android:textColor">
@android:color/white
</item>
<item name="android:gravity">
center
</item>
<item name="android:background">@drawable/comment_red_btn</item>
<item name="android:paddingTop">
@dimen/btn_top_bottom_padding
</item>
<item name="android:paddingBottom">
@dimen/btn_top_bottom_padding
</item>
<item name="android:layout_width">
match_parent
</item>
<item name="android:layout_height">
@dimen/btn_height
</item>
</style>
<style name="rec_top_btn" parent="@style/ds_rec_btn">
<item name="android:layout_marginStart">
25dp
</item>
<item name="android:layout_marginTop">
17dp
</item>
<item name="android:layout_marginEnd">
25.0dp
</item>
<item name="android:layout_marginBottom">
8dp
</item>
</style>
</resources>