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

@@ -31,35 +31,12 @@ android {
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
//工具集
implementation 'com.blankj:utilcodex:1.31.0'
//toast
implementation 'com.github.getActivity:ToastUtils:10.5'
//Gson
implementation 'com.google.code.gson:gson:2.8.5'
//网络
implementation 'com.github.bumptech.glide:okhttp3-integration:4.9.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation project(':baselibrary')
implementation project(':floatwindow')
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
//友盟统计SDK
implementation 'com.umeng.umsdk:common:9.4.7'// 必选
implementation 'com.umeng.umsdk:asms:1.4.1'// 必选
implementation 'com.umeng.umsdk:apm:1.5.2' // 错误分析升级为独立SDK看crash数据请一定集成可选
//自动更新
implementation 'com.teprinciple:updateapputilsx:2.3.0'
//ok
implementation 'com.lzy.net:okgo:3.0.4'
//qrcode
implementation 'com.github.yoojia:next-qrcode:2.0-2'
//QMUI
implementation 'com.qmuiteam:qmui:2.0.0-alpha10'
}

Binary file not shown.

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>

1
baselibrary/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

67
baselibrary/build.gradle Normal file
View File

@@ -0,0 +1,67 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
api 'androidx.core:core-ktx:1.3.2'
api 'androidx.appcompat:appcompat:1.3.1'
api 'com.google.android.material:material:1.4.0'
//工具集
api 'com.blankj:utilcodex:1.31.0'
//toast
api 'com.github.getActivity:ToastUtils:10.5'
//Gson
api 'com.google.code.gson:gson:2.8.5'
//网络
api 'com.github.bumptech.glide:okhttp3-integration:4.9.0'
//自动更新
api 'com.teprinciple:updateapputilsx:2.3.0'
//okgo
api 'com.lzy.net:okgo:3.0.4'
//qrcode
api 'com.github.yoojia:next-qrcode:2.0-2'
//QMUI
api 'com.qmuiteam:qmui:2.0.0-alpha10'
//recyclerview
api 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
//glide
api 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
//photoview
api 'com.github.chrisbanes:PhotoView:2.1.3'
//悬浮窗框架
api 'com.github.princekin-f:EasyFloat:1.3.4'
//leak
// debugApi 'com.squareup.leakcanary:leakcanary-android:1.5.4'
// releaseApi 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
// testApi 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
repositories {
mavenCentral()
}

21
baselibrary/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="base" />

View File

@@ -0,0 +1,95 @@
package base.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
/**
* Created by gallon on 2019/7/26.
*/
public abstract class RvSimpleAdapter<T> extends RecyclerView.Adapter<RvViewHolder> {
protected LayoutInflater mInflater;
public List<T> mData;
public int mHeaderCount = 0;
public Context mContext;
protected int mLayoutId;
public static abstract class OnItemClickListener {
public abstract void onItemClick(View view, int position, RvViewHolder holder);
public void onItemLongClick(View view, int position, RvViewHolder holder) {
}
}
private OnItemClickListener onItemClickListener;
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
public RvSimpleAdapter(Context context, List<T> data, int layoutId) {
this.mContext = context;
this.mData = data;
this.mLayoutId = layoutId;
mInflater = LayoutInflater.from(context);
}
@Override
public int getItemCount() {
return null == mData ? 0 : mData.size();
}
@Override
public void onBindViewHolder(final RvViewHolder holder, final int position) {
convert(holder, mData.get(holder.getLayoutPosition() - mHeaderCount), position);
setUpItemEvent(holder);
}
public abstract void convert(RvViewHolder holder, T bean, int position);
public void setUpItemEvent(final RvViewHolder holder) {
if (onItemClickListener != null) {
holder.itemView.setOnClickListener(v -> {
//这个获取位置的方法,防止添加删除导致位置不变
int layoutPosition = holder.getAdapterPosition() - mHeaderCount;
onItemClickListener.onItemClick(holder.itemView, layoutPosition, holder);
});
holder.itemView.setOnLongClickListener(v -> {
int layoutPosition = holder.getAdapterPosition() - mHeaderCount;
onItemClickListener.onItemLongClick(holder.itemView, layoutPosition, holder);
return false;
});
}
}
@Override
public RvViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RvViewHolder viewHolder = onCreateDefViewHolder(parent, viewType);
return viewHolder;
}
protected RvViewHolder onCreateDefViewHolder(ViewGroup parent, int viewType) {
return new RvViewHolder(mInflater.inflate(mLayoutId, parent, false));
}
public void addData(T datas) {
mData.add(mData.size(), datas);
notifyItemInserted(mData.size());
}
public void addData(int pos, T datas) {
mData.add(pos, datas);
notifyItemInserted(pos);
}
public void deleteData(int pos) {
mData.remove(pos);
notifyItemRemoved(pos);
}
}

View File

@@ -0,0 +1,59 @@
package base.adapter;
import android.graphics.Bitmap;
import android.util.SparseArray;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by gallon on 2019/7/26.
*/
public class RvViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;
public RvViewHolder(View itemView) {
super(itemView);
mViews = new SparseArray<View>();
}
//通过viewId获取控件
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
/**
* 设置TextView的值
*/
public RvViewHolder setText(int viewId, String text) {
TextView tv = getView(viewId);
tv.setText(text);
return this;
}
public RvViewHolder setImageResource(int viewId, int resId) {
ImageView view = getView(viewId);
view.setImageResource(resId);
return this;
}
public RvViewHolder setImageBitamp(int viewId, Bitmap bitmap) {
ImageView view = getView(viewId);
view.setImageBitmap(bitmap);
return this;
}
public RvViewHolder setImageURI(int viewId, String uri) {
ImageView view = getView(viewId);
// Imageloader.getInstance().loadImg(view,uri);
return this;
}
}

View File

@@ -0,0 +1,2 @@
<resources>
</resources>

View File

@@ -0,0 +1,2 @@
<resources>
</resources>

1
floatwindow/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

38
floatwindow/build.gradle Normal file
View File

@@ -0,0 +1,38 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
api 'androidx.core:core-ktx:1.3.2'
api 'androidx.appcompat:appcompat:1.3.1'
api 'com.google.android.material:material:1.4.0'
//工具集
api 'com.blankj:utilcodex:1.31.0'
}

21
floatwindow/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.yameida.floatwindow">
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:label="@string/app_name">
<service android:name=".DefaultFloatService" />
</application>
</manifest>

View File

@@ -0,0 +1,706 @@
package org.yameida.floatwindow;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.provider.Settings;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import com.blankj.utilcode.util.BarUtils;
import com.blankj.utilcode.util.ScreenUtils;
import com.blankj.utilcode.util.Utils;
import org.yameida.floatwindow.view.HiderView;
public abstract class BaseFloatWindow extends Service {
protected final String TAG = this.getClass().getSimpleName();
/**
* 悬浮球 坐落 左 右 标记
*/
public static final int LEFT = 0;
public static final int RIGHT = 1;
/**
* 记录 logo 停放的位置,以备下次恢复
*/
protected final String LOCATION_X = TAG + "_x";
protected final String LOCATION_Y = TAG + "_y";
/**
* 停靠默认位置
*/
protected int mDefaultLocation = RIGHT;
/**
* 悬浮窗 坐落 位置
*/
protected int mHintLocation = mDefaultLocation;
/**
* 记录当前手指位置在屏幕上的横坐标值
*/
private float mXInScreen;
/**
* 记录当前手指位置在屏幕上的纵坐标值
*/
private float mYInScreen;
/**
* 记录手指按下时在屏幕上的横坐标的值
*/
private float mXDownInScreen;
/**
* 记录手指按下时在屏幕上的纵坐标的值
*/
private float mYDownInScreen;
/**
* 记录手指按下时在小悬浮窗的View上的横坐标的值
*/
private float mXInView;
/**
* 记录手指按下时在小悬浮窗的View上的纵坐标的值
*/
private float mYinview;
/**
* 记录屏幕的宽度
*/
private int mScreenWidth;
protected WindowManager mWindowManager;
protected WindowManager.LayoutParams params;
private Context context = Utils.getApp();
/**
* 退出悬浮窗区域
*/
private HiderView mHiderView = new HiderView(context);
private boolean showHider = true;
/**
* 用于 定时 隐藏 logo的定时器
*/
private CountDownTimer mHideTimer;
/**
* float menu的高度
*/
private Handler mHandler = new Handler(Looper.getMainLooper());
/**
* 悬浮窗左右移动到默认位置 动画的 加速器
*/
private Interpolator mLinearInterpolator = new LinearInterpolator();
/**
* 标记是否拖动中
*/
private boolean isDrag = false;
/**
* 用于恢复悬浮球的location的属性动画值
*/
private int mResetLocationValue;
/**
* 限制拖动的y轴坐标
*/
protected int minY = 0;
protected int maxY = ScreenUtils.getScreenHeight();
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 这个事件用于处理移动、自定义点击或者其它事情return true可以保证onclick事件失效
*/
private View.OnTouchListener touchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
floatEventDown(event);
break;
case MotionEvent.ACTION_MOVE:
floatEventMove(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
floatEventUp();
break;
}
return true;
}
};
ValueAnimator valueAnimator = null;
protected boolean isExtended = false;
protected View logoView;
protected View rightView;
protected View leftView;
private GetViewCallback mGetViewCallback;
protected BaseFloatWindow() {
initFloatWindow();
initTimer();
initFloatView();
}
private void initFloatView() {
LayoutInflater inflater = LayoutInflater.from(context);
logoView = mGetViewCallback == null ? getLogoView(inflater) : mGetViewCallback.getLogoView(inflater);
leftView = mGetViewCallback == null ? getLeftView(inflater) : mGetViewCallback.getLeftView(inflater);
rightView = mGetViewCallback == null ? getRightView(inflater) : mGetViewCallback.getRightView(inflater);
if (logoView == null) {
throw new IllegalArgumentException("Must impl GetViewCallback or impl " + this.getClass().getSimpleName() + "and make getLogoView() not return null !");
}
logoView.setOnTouchListener(touchListener);//恢复touch事件
}
/**
* 初始化 隐藏悬浮球的定时器
*/
private void initTimer() {
mHideTimer = new CountDownTimer(2000, 10) { //悬浮窗超过5秒没有操作的话会自动隐藏
@Override
public void onTick(long millisUntilFinished) {
if (isExtended) {
mHideTimer.cancel();
}
}
@Override
public void onFinish() {
if (isExtended) {
mHideTimer.cancel();
return;
}
if (!isDrag) {
if (mHintLocation == LEFT) {
if (mGetViewCallback == null) {
shrinkLeftLogoView(logoView);
} else {
mGetViewCallback.shrinkLeftLogoView(logoView);
}
} else {
if (mGetViewCallback == null) {
shrinkRightLogoView(logoView);
} else {
mGetViewCallback.shrinkRightLogoView(logoView);
}
}
}
}
};
}
/**
* 初始化悬浮球 window
*/
private void initFloatWindow() {
params = new WindowManager.LayoutParams();
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(context)) {
if (Build.VERSION.SDK_INT >= 26) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
} else {
if (Build.VERSION.SDK_INT >= 26) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
}
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
mScreenWidth = mWindowManager.getDefaultDisplay().getWidth();
int screenHeigth = mWindowManager.getDefaultDisplay().getHeight();
params.format = PixelFormat.RGBA_8888;
params.gravity = Gravity.LEFT | Gravity.TOP;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
mHintLocation = getSetting(LOCATION_X, mDefaultLocation);
int defaultY = ((screenHeigth) / 2) / 3;
int y = getSetting(LOCATION_Y, defaultY);
if (mHintLocation == LEFT) {
params.x = 0;
} else {
params.x = mScreenWidth;
}
if (y != 0 && y != defaultY) {
params.y = y;
} else {
params.y = defaultY;
}
params.alpha = 1;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
}
/**
* 悬浮窗touch事件的 down 事件
*/
private void floatEventDown(MotionEvent event) {
isDrag = false;
mHideTimer.cancel();
if (mGetViewCallback == null) {
resetLogoViewSize(mHintLocation, logoView);
} else {
mGetViewCallback.resetLogoViewSize(mHintLocation, logoView);
}
mXInView = event.getX();
mYinview = event.getY();
mXDownInScreen = event.getRawX();
mYDownInScreen = event.getRawY();
mXInScreen = event.getRawX();
mYInScreen = event.getRawY();
}
/**
* 悬浮窗touch事件的 move 事件
*/
private void floatEventMove(MotionEvent event) {
mXInScreen = event.getRawX();
mYInScreen = event.getRawY();
//连续移动的距离超过3则更新一次视图位置
if (Math.abs(mXInScreen - mXDownInScreen) > logoView.getWidth() / 4 || Math.abs(mYInScreen - mYDownInScreen) > logoView.getWidth() / 4) {
params.x = (int) (mXInScreen - mXInView);
params.y = (int) (mYInScreen - mYinview) - logoView.getHeight() / 2;
updateViewPosition(); // 手指移动的时候更新小悬浮窗的位置
// double a = mScreenWidth / 2;
// float offset = (float) ((a - (Math.abs(params.x - a))) / a);
// if (mGetViewCallback == null) {
// dragLogoViewOffset(logoView, isDrag, false, offset);
// } else {
// mGetViewCallback.dragLogoViewOffset(logoView, isDrag, false, offset);
// }
mHiderView.attachToWindow();
} else {
// isDrag = false;
//// logoView.setDrag(false, 0, true);
// if (mGetViewCallback == null) {
// dragLogoViewOffset(logoView, false, true, 0);
// } else {
// mGetViewCallback.dragLogoViewOffset(logoView, false, true, 0);
// }
}
}
/**
* 悬浮窗touch事件的 up 事件
*/
private void floatEventUp() {
if (mXInScreen < mScreenWidth / 2) { //在左边
mHintLocation = LEFT;
} else { //在右边
mHintLocation = RIGHT;
}
valueAnimator = ValueAnimator.ofInt(64);
valueAnimator.setInterpolator(mLinearInterpolator);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mResetLocationValue = (int) animation.getAnimatedValue();
mHandler.post(updatePositionRunnable);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (Math.abs(params.x) < 0) {
params.x = 0;
} else if (Math.abs(params.x) > mScreenWidth) {
params.x = mScreenWidth;
}
updateViewPosition();
isDrag = false;
if (mGetViewCallback == null) {
dragLogoViewOffset(logoView, false, true, 0);
} else {
mGetViewCallback.dragLogoViewOffset(logoView, false, true, 0);
}
mHideTimer.start();
}
@Override
public void onAnimationCancel(Animator animation) {
if (Math.abs(params.x) < 0) {
params.x = 0;
} else if (Math.abs(params.x) > mScreenWidth) {
params.x = mScreenWidth;
}
updateViewPosition();
isDrag = false;
if (mGetViewCallback == null) {
dragLogoViewOffset(logoView, false, true, 0);
} else {
mGetViewCallback.dragLogoViewOffset(logoView, false, true, 0);
}
mHideTimer.start();
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
if (!valueAnimator.isRunning()) {
valueAnimator.start();
}
//这里需要判断如果如果手指所在位置和logo所在位置在一个宽度内则不移动,
if (Math.abs(mXInScreen - mXDownInScreen) > logoView.getWidth() / 5F || Math.abs(mYInScreen - mYDownInScreen) > logoView.getHeight() / 5F) {
isDrag = false;
} else {
openMenu();
}
mHiderView.release();
if (mHiderView.mLp != null) {
int lx = mScreenWidth / 2 - mHiderView.getWidth() / 2;
int ly = mHiderView.mLp.y + BarUtils.getStatusBarHeight();
int rx = lx + mHiderView.getWidth() + logoView.getWidth();
int ry = ly + mHiderView.getHeight() + logoView.getHeight();
if (mXInScreen >= lx && mXInScreen <= rx
&& mYInScreen >= ly && mYInScreen <= ry) {
valueAnimator.cancel();
// 复原X
if (mXDownInScreen < mScreenWidth / 2) { //在左边
mHintLocation = LEFT;
params.x = 0;
} else { //在右边
mHintLocation = RIGHT;
params.x = mScreenWidth;
}
// 复原Y
params.y = (int) mYDownInScreen;
// 滑动的位置在隐藏视图内
hide();
}
}
}
/**
* 手指离开屏幕后 用于恢复 悬浮球的 logo 的左右位置
*/
private Runnable updatePositionRunnable = new Runnable() {
@Override
public void run() {
isDrag = true;
checkPosition();
}
};
/**
* 用于检查并更新悬浮球的位置
*/
private void checkPosition() {
if (params.x > 0 && params.x < mScreenWidth) {
if (mHintLocation == LEFT) {
params.x = params.x - mResetLocationValue;
} else {
params.x = params.x + mResetLocationValue;
}
updateViewPosition();
double a = mScreenWidth / 2;
float offset = (float) ((a - (Math.abs(params.x - a))) / a);
// logoView.setDrag(isDrag, offset, true);
if (mGetViewCallback == null) {
dragLogoViewOffset(logoView, false, true, 0);
} else {
mGetViewCallback.dragLogoViewOffset(logoView, isDrag, true, offset);
}
return;
}
if (Math.abs(params.x) < 0) {
params.x = 0;
} else if (Math.abs(params.x) > mScreenWidth) {
params.x = mScreenWidth;
}
if (valueAnimator.isRunning()) {
valueAnimator.cancel();
}
updateViewPosition();
isDrag = false;
}
public void show() {
try {
dismiss();
if (mWindowManager != null && params != null && logoView != null) {
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowManager.addView(logoView, params);
}
if (mHideTimer != null) {
mHideTimer.start();
} else {
initTimer();
mHideTimer.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void hide() {
dismiss();
}
/**
* 打开菜单/关闭菜单
*/
protected void openMenu() {
if (isDrag) return;
if (!isExtended) {
// logoView.setDrawDarkBg(false);
try {
mWindowManager.removeViewImmediate(logoView);
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
if (mHintLocation == RIGHT) {
mWindowManager.addView(rightView, params);
if (mGetViewCallback == null) {
rightViewOpened(rightView);
} else {
mGetViewCallback.rightViewOpened(rightView);
}
} else {
mWindowManager.addView(leftView, params);
if (mGetViewCallback == null) {
leftViewOpened(leftView);
} else {
mGetViewCallback.leftViewOpened(leftView);
}
}
} catch (Exception e) {
e.printStackTrace();
}
isExtended = true;
mHideTimer.cancel();
} else {
// logoView.setDrawDarkBg(true);
try {
mWindowManager.removeViewImmediate(mHintLocation == LEFT ? leftView : rightView);
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowManager.addView(logoView, params);
if (mGetViewCallback == null) {
leftOrRightViewClosed(logoView);
} else {
mGetViewCallback.leftOrRightViewClosed(logoView);
}
} catch (Exception e) {
e.printStackTrace();
}
isExtended = false;
mHideTimer.start();
}
}
/**
* 更新悬浮窗在屏幕中的位置。
*/
private void updateViewPosition() {
isDrag = true;
try {
if (!isExtended) {
if (minY == 0) minY = logoView.getHeight();
if (maxY == ScreenUtils.getScreenHeight()) maxY -= logoView.getHeight();
if (params.y < minY) params.y = minY;
if (params.y > maxY) params.y = maxY;
mWindowManager.updateViewLayout(logoView, params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 移除所有悬浮窗 释放资源
*/
public void dismiss() {
//记录上次的位置logo的停放位置以备下次恢复
saveSetting(LOCATION_X, mHintLocation);
saveSetting(LOCATION_Y, params.y);
logoView.clearAnimation();
try {
mHideTimer.cancel();
if (isExtended) {
mWindowManager.removeViewImmediate(mHintLocation == LEFT ? leftView : rightView);
} else {
if (logoView.getParent() != null) {
mWindowManager.removeViewImmediate(logoView);
}
}
isExtended = false;
isDrag = false;
if (mGetViewCallback == null) {
onDestroyed();
} else {
mGetViewCallback.onDestroyed();
}
} catch (Exception e) {
e.printStackTrace();
}
}
protected abstract View getLeftView(LayoutInflater inflater);
protected abstract View getRightView(LayoutInflater inflater);
protected abstract View getLogoView(LayoutInflater inflater);
protected abstract void resetLogoViewSize(int hintLocation, View logoView);//logo恢复原始大小
protected abstract void dragLogoViewOffset(View logoView, boolean isDrag, boolean isResetPosition, float offset);
protected abstract void shrinkLeftLogoView(View logoView);//logo左边收缩
protected abstract void shrinkRightLogoView(View logoView);//logo右边收缩
protected abstract void leftViewOpened(View leftView);//左菜单打开
protected abstract void rightViewOpened(View rightView);//右菜单打开
protected abstract void leftOrRightViewClosed(View logoView);
protected abstract void onDestroyed();
public interface GetViewCallback {
View getLeftView(LayoutInflater inflater);
View getRightView(LayoutInflater inflater);
View getLogoView(LayoutInflater inflater);
void resetLogoViewSize(int hintLocation, View logoView);//logo恢复原始大小
void dragLogoViewOffset(View logoView, boolean isDrag, boolean isResetPosition, float offset);//logo正被拖动或真在恢复原位
void shrinkLeftLogoView(View logoView);//logo左边收缩
void shrinkRightLogoView(View logoView);//logo右边收缩
void leftViewOpened(View leftView);//左菜单打开
void rightViewOpened(View rightView);//右菜单打开
void leftOrRightViewClosed(View logoView);
void onDestroyed();
}
/**
* 用于保存悬浮球的位置记录
*
* @param key String
* @param defaultValue int
* @return int
*/
private int getSetting(String key, int defaultValue) {
try {
SharedPreferences sharedata = context.getSharedPreferences("floatLogo", 0);
return sharedata.getInt(key, defaultValue);
} catch (Exception e) {
e.printStackTrace();
}
return defaultValue;
}
/**
* 用于保存悬浮球的位置记录
*
* @param key String
* @param value int
*/
public void saveSetting(String key, int value) {
try {
SharedPreferences.Editor sharedata = context.getSharedPreferences("floatLogo", 0).edit();
sharedata.putInt(key, value);
sharedata.apply();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
} else {
}
mScreenWidth = mWindowManager.getDefaultDisplay().getWidth();
if (mHintLocation == RIGHT) {
params.x = mScreenWidth;
}
show();
}
}

View File

@@ -0,0 +1,192 @@
package org.yameida.floatwindow
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import com.blankj.utilcode.util.*
import org.yameida.floatwindow.listener.FloatWindowListener
import kotlinx.android.synthetic.main.layout_menu_left.view.*
import kotlinx.android.synthetic.main.layout_menu_logo.view.*
import kotlinx.android.synthetic.main.layout_menu_right.view.*
import org.yameida.floatwindow.listener.OnClickListener
/**
* Created by Gallon on 2019/9/7.
*/
class DefaultFloatService : BaseFloatWindow(), View.OnClickListener {
// private var currentStatus = RecordStatusManager.PLAY_STATUS_STOP
private var active = false
private var context = Utils.getApp()
private var manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val CHANNEL_ID_STRING = "907"
var floatWindowListener: FloatWindowListener? = null
var onClickListener: OnClickListener? = null
inner class DefaultFloatServiceBinder : Binder() {
fun getService() = this@DefaultFloatService
}
init {
minY = SizeUtils.dp2px(100F)
maxY = ScreenUtils.getScreenHeight() - SizeUtils.dp2px(150F)
}
override fun onBind(intent: Intent?): IBinder? = DefaultFloatServiceBinder()
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val mChannel = NotificationChannel(CHANNEL_ID_STRING, "float", NotificationManager.IMPORTANCE_HIGH)
manager.createNotificationChannel(mChannel)
val notification = Notification.Builder(context, CHANNEL_ID_STRING).build()
startForeground(1, notification)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
LogUtils.d(TAG, "onStartCommand: ${intent?.data}")
show()
return super.onStartCommand(intent, flags, startId)
}
override fun show() {
super.show()
// FloatWindowManager.isShow = true
floatWindowListener?.show()
}
override fun hide() {
super.hide()
// FloatWindowManager.isShow = false
floatWindowListener?.hide()
}
override fun onClick(v: View) {
LogUtils.v(TAG, "float onClick: ")
openMenu()
if (v == leftView.fl_window_background_left || v == rightView.fl_window_background_right) {
onClickListener?.onClick(v,-1)
}
if (v == leftView.iv_logo_left || v == rightView.iv_logo_right || v == leftView.iv_logo_left2 || v == rightView.iv_logo_right2) {
onClickListener?.onClick(v, 0)
}
if (v == leftView.iv_start_left || v == rightView.iv_start_right) {
onClickListener?.onClick(v,1)
}
if (v == leftView.iv_shot_left || v == rightView.iv_shot_right) {
onClickListener?.onClick(v,2)
}
if (v == leftView.iv_back_left || v == rightView.iv_back_right) {
onClickListener?.onClick(v,3)
}
if (v == leftView.iv_settings_left || v == rightView.iv_settings_right) {
onClickListener?.onClick(v,4)
}
if (v == leftView.iv_resume_pause_left || v == rightView.iv_resume_pause_right) {
onClickListener?.onClick(v,5)
}
if (v == leftView.iv_stop_left || v == rightView.iv_stop_right) {
onClickListener?.onClick(v,6)
}
}
override fun getLeftView(inflater: LayoutInflater): View {
leftView = inflater.inflate(R.layout.layout_menu_left, null)
leftView.iv_logo_left.setOnClickListener(this)
leftView.iv_logo_left2.setOnClickListener(this)
leftView.iv_start_left.setOnClickListener(this)
leftView.iv_shot_left.setOnClickListener(this)
leftView.iv_back_left.setOnClickListener(this)
leftView.iv_resume_pause_left.setOnClickListener(this)
leftView.iv_stop_left.setOnClickListener(this)
leftView.iv_settings_left.setOnClickListener(this)
leftView.fl_window_background_left.setOnClickListener(this)
return leftView
}
override fun getRightView(inflater: LayoutInflater): View {
rightView = inflater.inflate(R.layout.layout_menu_right, null)
rightView.iv_logo_right.setOnClickListener(this)
rightView.iv_logo_right2.setOnClickListener(this)
rightView.iv_start_right.setOnClickListener(this)
rightView.iv_shot_right.setOnClickListener(this)
rightView.iv_back_right.setOnClickListener(this)
rightView.iv_resume_pause_right.setOnClickListener(this)
rightView.iv_stop_right.setOnClickListener(this)
rightView.iv_settings_right.setOnClickListener(this)
rightView.fl_window_background_right.setOnClickListener(this)
return rightView
}
override fun getLogoView(inflater: LayoutInflater): View {
return inflater.inflate(R.layout.layout_menu_logo, null)
}
override fun resetLogoViewSize(hintLocation: Int, logoView: View) {
logoView.clearAnimation()
logoView.translationX = 0f
logoView.scaleX = 1f
logoView.scaleY = 1f
logoView.alpha = 1f
logoView.tv_float_time.textSize = 10f
}
override fun dragLogoViewOffset(logoView: View, isDrag: Boolean, isResetPosition: Boolean, offset: Float) {
if (isDrag && offset > 0) {
logoView.scaleX = 1 + offset
logoView.scaleY = 1 + offset
} else {
logoView.translationX = 0f
logoView.scaleX = 1f
logoView.scaleY = 1f
}
logoView.rotation = offset * 360
}
public override fun shrinkLeftLogoView(smallView: View) {
smallView.translationX = (-smallView.width / 4).toFloat()
smallView.alpha = 0.35f
logoView.tv_float_time.textSize = 7f
}
public override fun shrinkRightLogoView(smallView: View) {
smallView.translationX = (smallView.width / 4).toFloat()
smallView.alpha = 0.35f
logoView.tv_float_time.textSize = 7f
}
public override fun leftViewOpened(leftView: View) {
val layoutParams = leftView.fl_window_measure_left.layoutParams as FrameLayout.LayoutParams
leftView.fl_window_measure_left.measure(0, 0)
layoutParams.topMargin = (params.y - leftView.fl_window_measure_left.measuredHeight / 2 - leftView.iv_logo_left.measuredHeight / 2)
leftView.fl_window_measure_left.layoutParams = layoutParams
// ToastUtils.showShort("左边的菜单被打开了")
}
public override fun rightViewOpened(rightView: View) {
val layoutParams = rightView.fl_window_measure_right.layoutParams as FrameLayout.LayoutParams
rightView.fl_window_measure_right.measure(0, 0)
layoutParams.topMargin = params.y - rightView.fl_window_measure_right.measuredHeight / 2 - rightView.iv_logo_right.measuredHeight / 2
layoutParams.leftMargin = ScreenUtils.getScreenWidth() - rightView.fl_window_measure_right.measuredWidth
rightView.fl_window_measure_right.layoutParams = layoutParams
// ToastUtils.showShort("右边的菜单被打开了")
}
public override fun leftOrRightViewClosed(smallView: View) {
// Toast.makeText(context, "菜单被关闭了", Toast.LENGTH_SHORT).show()
}
override fun onDestroyed() {}
}

View File

@@ -0,0 +1,39 @@
package org.yameida.floatwindow
import android.content.Intent
import android.os.Build
import com.blankj.utilcode.util.Utils
/**
* Created by Gallon on 2019/9/5.
*/
object FloatWindowManager {
private val TAG = FloatWindowManager::class.java.simpleName
private var context = Utils.getApp()
fun show(service: Class<out BaseFloatWindow>, intent: Intent? = null) {
startServiceSafe(Intent(context, service).apply {
if (intent != null) {
this.putExtras(intent)
}
})
}
fun hide(service: Class<out BaseFloatWindow>, intent: Intent? = null) {
startServiceSafe(Intent(context, service).apply {
if (intent != null) {
this.putExtras(intent)
}
})
}
private fun startServiceSafe(intent: Intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
}

View File

@@ -0,0 +1,10 @@
package org.yameida.floatwindow.listener
/**
* Created by Gallon on 2019/8/11.
*/
interface FloatWindowListener {
fun show()
fun hide()
}

View File

@@ -0,0 +1,11 @@
package org.yameida.floatwindow.listener
import android.view.View
/**
* Created by Gallon on 2019/8/11.
*/
interface OnClickListener {
fun onClick(v: View, event: Int)
}

View File

@@ -0,0 +1,118 @@
package org.yameida.floatwindow.view;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.WindowManager;
import android.widget.LinearLayout;
import org.yameida.floatwindow.R;
public class HiderView extends LinearLayout {
private Context mContext;
private WindowManager mWindowManager;
public WindowManager.LayoutParams mLp;
private ValueAnimator valueAnimator;
private int mResetLocationValue;
public HiderView(Context context) {
super(context);
this.mContext = context;
init();
}
public HiderView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
private void init() {
inflate(mContext, R.layout.widget_hiderview_ll, this);
}
public void attachToWindow() {
if (this.getParent() != null) {
return;
}
mWindowManager = (WindowManager) mContext
.getSystemService(Context.WINDOW_SERVICE);
mLp = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(getContext())) {
if (Build.VERSION.SDK_INT >= 26) {
mLp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
} else {
if (Build.VERSION.SDK_INT >= 26) {
mLp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
}
} else {
mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
// mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
mLp.format = PixelFormat.RGBA_8888;
mLp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLp.gravity = Gravity.CENTER | Gravity.TOP;
mLp.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLp.height = WindowManager.LayoutParams.WRAP_CONTENT;
int y = mWindowManager.getDefaultDisplay().getHeight();
mLp.y = y;
mWindowManager.addView(this, mLp);
valueAnimator = ValueAnimator.ofInt(0, y / 4);
valueAnimator.setDuration(200);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mResetLocationValue = (int) animation.getAnimatedValue();
mLp.y = y - getHeight() - mResetLocationValue;
mWindowManager.updateViewLayout(HiderView.this, mLp);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {
mWindowManager.updateViewLayout(HiderView.this, mLp);
}
@Override
public void onAnimationCancel(Animator animation) {
mWindowManager.updateViewLayout(HiderView.this, mLp);
}
@Override
public void onAnimationRepeat(Animator animation) {}
});
if (!valueAnimator.isRunning()) {
valueAnimator.start();
}
}
public void release() {
if (this.getParent() != null) {
if (valueAnimator.isRunning()) {
valueAnimator.cancel();
}
mWindowManager.removeView(this);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl_window_background_left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#66333333">
<FrameLayout
android:id="@+id/fl_window_measure_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="190dp"
android:visibility="visible">
<ImageView
android:id="@+id/iv_start_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginEnd="@dimen/float_margin_start"
android:src="@drawable/float_icon_record" />
<ImageView
android:id="@+id/iv_shot_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="@dimen/float_margin_start"
android:layout_marginTop="29dp"
android:src="@drawable/float_icon_pause" />
<ImageView
android:id="@+id/iv_back_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_below="@id/iv_shot_left"
android:layout_marginStart="@dimen/float_margin_start"
android:layout_marginTop="2dp"
android:src="@drawable/float_icon_home" />
<ImageView
android:id="@+id/iv_logo_left"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_below="@id/iv_start_left"
android:layout_marginStart="5dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="55dp"
android:src="@mipmap/ic_launcher_round" />
<ImageView
android:id="@+id/iv_settings_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_below="@id/iv_logo_left"
android:layout_marginTop="10dp"
android:layout_marginEnd="@dimen/float_margin_start"
android:scaleX="1.05"
android:scaleY="1.05"
android:src="@drawable/float_icon_setting" />
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="190dp"
android:gravity="center_vertical"
android:paddingStart="0dp"
android:paddingEnd="2dp"
android:visibility="gone">
<ImageView
android:id="@+id/iv_logo_left2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="2dp"
android:src="@mipmap/ic_launcher_round" />
<ImageView
android:id="@+id/iv_resume_pause_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="2dp"
android:src="@drawable/float_icon_pause" />
<ImageView
android:id="@+id/iv_stop_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="-5dp"
android:src="@drawable/float_icon_stop" />
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="@dimen/float_stand_size"
android:gravity="center">
<ImageView
android:layout_width="@dimen/float_stand_size"
android:layout_height="@dimen/float_stand_size"
android:scaleType="centerInside"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:id="@+id/tv_float_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/white"
android:textSize="10sp"
android:visibility="gone" />
</FrameLayout>

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl_window_background_right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#66333333">
<FrameLayout
android:id="@+id/fl_window_measure_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="190dp"
android:visibility="visible">
<ImageView
android:id="@+id/iv_start_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="@dimen/float_margin_start"
android:src="@drawable/float_icon_record" />
<ImageView
android:id="@+id/iv_shot_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginTop="29dp"
android:src="@drawable/float_icon_pause" />
<ImageView
android:id="@+id/iv_back_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_below="@id/iv_shot_right"
android:layout_marginTop="2dp"
android:src="@drawable/float_icon_home" />
<ImageView
android:id="@+id/iv_logo_right"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_below="@id/iv_start_right"
android:layout_marginStart="55dp"
android:layout_marginTop="10dp"
android:src="@mipmap/ic_launcher_round" />
<ImageView
android:id="@+id/iv_settings_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_below="@id/iv_logo_right"
android:layout_marginStart="@dimen/float_margin_start"
android:layout_marginTop="10dp"
android:scaleX="1.05"
android:scaleY="1.05"
android:src="@drawable/float_icon_setting" />
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="190dp"
android:gravity="center_vertical"
android:paddingEnd="2dp"
android:paddingStart="0dp"
android:visibility="gone">
<ImageView
android:id="@+id/iv_stop_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:src="@drawable/float_icon_stop" />
<ImageView
android:id="@+id/iv_resume_pause_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="-5dp"
android:src="@drawable/float_icon_pause" />
<ImageView
android:id="@+id/iv_logo_right2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="2dp"
android:src="@mipmap/ic_launcher_round" />
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@@ -0,0 +1,15 @@
<?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="wrap_content"
android:gravity="center"
android:orientation="horizontal"
>
<!-- android:background="@color/balloonperformer_translucent"-->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/widget_float_hide" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background">@color/color_f5f5f5</color>
<color name="list_divider_line">@color/color_e5e5e5</color>
<color name="white">#fff</color>
<color name="color_333333">#333333</color>
<color name="color_999999">#999999</color>
<color name="color_b2000000">#b2000000</color>
<color name="color_dashen">#ff5215</color>
<color name="color_dashen_passed">#e54b12</color>
<color name="float_time_color">#f58220</color>
<color name="color_e5e5e5">#e5e5e5</color>
<color name="color_f5f5f5">#f5f5f5</color>
<color name="c8c8c8">#c8c8c8</color>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="bar_gray">#37474F</color>
<color name="while_bg">#F7F7F7</color>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="setting_start_padding">22dp</dimen>
<dimen name="setting_end_start_padding">25dp</dimen>
<dimen name="setting_end_padding">10dp</dimen>
<dimen name="setting_vertical_padding">10dp</dimen>
<dimen name="setting_end_font_width">60dp</dimen>
<dimen name="setting_start_image_width">20dp</dimen>
<dimen name="size_button">18sp</dimen>
<dimen name="btn_top_bottom_padding">10dp</dimen>
<dimen name="btn_height">50dp</dimen>
<dimen name="float_size">60dp</dimen>
<dimen name="float_margin_start">50dp</dimen>
<dimen name="float_stand_size">36dp</dimen>
<dimen name="setting_start_font_size">15sp</dimen>
<dimen name="setting_end_font_size">13sp</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">floatwindow</string>
</resources>

View File

@@ -1,2 +1,3 @@
include ':app'
include ':app', ':baselibrary'
include ':floatwindow'
rootProject.name = "WorkTool"