新增:发送通道 URL Scheme(支持跨应用数据传递)#250

pull/286/head
pppscn 2 years ago
parent 65e861ba62
commit 992fc2eb8c

@ -37,6 +37,7 @@ data class Sender(
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
TYPE_URL_SCHEME -> R.drawable.icon_url_scheme
else -> R.drawable.icon_sms
}
@ -58,6 +59,7 @@ data class Sender(
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
TYPE_URL_SCHEME -> R.drawable.icon_url_scheme
else -> R.drawable.icon_sms
}

@ -0,0 +1,7 @@
package com.idormy.sms.forwarder.entity.setting
import java.io.Serializable
data class UrlSchemeSetting(
var urlScheme: String,
) : Serializable

@ -109,6 +109,7 @@ class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAda
TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java
TYPE_URL_SCHEME -> UrlSchemeFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)
@ -134,6 +135,7 @@ class SendersFragment : BaseFragment<FragmentSendersBinding?>(), SenderPagingAda
TYPE_GOTIFY -> GotifyFragment::class.java
TYPE_DINGTALK_INNER_ROBOT -> DingtalkInnerRobotFragment::class.java
TYPE_FEISHU_APP -> FeishuAppFragment::class.java
TYPE_URL_SCHEME -> UrlSchemeFragment::class.java
else -> DingtalkGroupRobotFragment::class.java
}
).setNewActivity(true)

@ -0,0 +1,227 @@
package com.idormy.sms.forwarder.fragment.senders
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
import com.idormy.sms.forwarder.databinding.FragmentSendersUrlSchemeBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.UrlSchemeSetting
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.sender.UrlSchemeUtils
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.*
@Page(name = "URL Scheme")
@Suppress("PrivatePropertyName")
class UrlSchemeFragment : BaseFragment<FragmentSendersUrlSchemeBinding?>(), View.OnClickListener {
private val TAG: String = UrlSchemeFragment::class.java.simpleName
var titleBar: TitleBar? = null
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
private var mCountDownHelper: CountDownButtonHelper? = null
@JvmField
@AutoWired(name = KEY_SENDER_ID)
var senderId: Long = 0
@JvmField
@AutoWired(name = KEY_SENDER_TYPE)
var senderType: Int = 0
@JvmField
@AutoWired(name = KEY_SENDER_CLONE)
var isClone: Boolean = false
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentSendersUrlSchemeBinding {
return FragmentSendersUrlSchemeBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.url_scheme)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, SettingUtils.requestTimeout)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
binding!!.btnTest.text = getString(R.string.test)
}
})
//新增
if (senderId <= 0) {
titleBar?.setSubTitle(getString(R.string.add_sender))
binding!!.btnDel.setText(R.string.discard)
return
}
//编辑
binding!!.btnDel.setText(R.string.del)
AppDatabase.getInstance(requireContext())
.senderDao()
.get(senderId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<Sender> {
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onSuccess(sender: Sender) {
if (isClone) {
titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name)
binding!!.btnDel.setText(R.string.discard)
} else {
titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name)
}
binding!!.etName.setText(sender.name)
binding!!.sbEnable.isChecked = sender.status == 1
val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java)
Log.d(TAG, settingVo.toString())
if (settingVo != null) {
binding!!.etUrlScheme.setText(settingVo.urlScheme)
}
}
})
}
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).observe(this) { mCountDownHelper?.finish() }
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_test -> {
mCountDownHelper?.start()
Thread {
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info))
UrlSchemeUtils.sendMsg(settingVo, msgInfo)
} catch (e: Exception) {
e.printStackTrace()
if (Looper.myLooper() == null) Looper.prepare()
XToastUtils.error(e.message.toString())
Looper.loop()
}
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).post("finish")
}.start()
return
}
R.id.btn_del -> {
if (senderId <= 0 || isClone) {
popToBack()
return
}
MaterialDialog.Builder(requireContext())
.title(R.string.delete_sender_title)
.content(R.string.delete_sender_tips)
.positiveText(R.string.lab_yes)
.negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(senderId)
XToastUtils.success(R.string.delete_sender_toast)
popToBack()
}
.show()
return
}
R.id.btn_save -> {
val name = binding!!.etName.text.toString().trim()
if (TextUtils.isEmpty(name)) {
throw Exception(getString(R.string.invalid_name))
}
val status = if (binding!!.sbEnable.isChecked) 1 else 0
val settingVo = checkSetting()
if (isClone) senderId = 0
val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status)
Log.d(TAG, senderNew.toString())
viewModel.insertOrUpdate(senderNew)
XToastUtils.success(R.string.tipSaveSuccess)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
e.printStackTrace()
}
}
private fun checkSetting(): UrlSchemeSetting {
val urlScheme = binding!!.etUrlScheme.text.toString().trim()
if (!CommonUtils.checkUrlScheme(urlScheme, false)) {
throw Exception(getString(R.string.invalid_url_scheme))
}
//TODO:判断Sheme是否有效
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlScheme))
val packageManager: PackageManager = requireContext().packageManager
val activities: List<ResolveInfo> = packageManager.queryIntentActivities(intent, 0)
if (activities.isEmpty()) {
throw Exception(getString(R.string.invalid_url_scheme))
}
return UrlSchemeSetting(urlScheme)
}
override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
}

@ -249,6 +249,20 @@ class CommonUtils private constructor() {
return mat.matches()
}
//是否合法的URL Scheme
fun checkUrlScheme(urls: String?): Boolean {
return checkUrlScheme(urls, false)
}
//是否合法的URL Scheme
fun checkUrlScheme(urls: String?, emptyResult: Boolean): Boolean {
if (TextUtils.isEmpty(urls)) return emptyResult
val regex = "^[a-zA-Z0-9]+://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]"
val pat = Pattern.compile(regex)
val mat = pat.matcher(urls?.trim() ?: "")
return mat.matches()
}
//是否启用通知监听服务
fun isNotificationListenerServiceEnabled(context: Context): Boolean {
val packageNames = NotificationManagerCompat.getEnabledListenerPackages(context)

@ -166,6 +166,7 @@ const val TYPE_PUSHPLUS = 10
const val TYPE_GOTIFY = 11
const val TYPE_DINGTALK_INNER_ROBOT = 12
const val TYPE_FEISHU_APP = 13
const val TYPE_URL_SCHEME = 14
var SENDER_FRAGMENT_LIST = listOf(
PageInfo(
getString(R.string.dingtalk_robot),
@ -265,6 +266,13 @@ var SENDER_FRAGMENT_LIST = listOf(
CoreAnim.slide,
R.drawable.icon_feishu_app
),
PageInfo(
getString(R.string.url_scheme),
"com.idormy.sms.forwarder.fragment.senders.UrlSchemeFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_url_scheme
),
)
//前台服务

@ -126,6 +126,10 @@ object SendUtils {
val settingVo = Gson().fromJson(sender.jsonSetting, FeishuAppSetting::class.java)
FeishuAppUtils.sendMsg(settingVo, msgInfo, rule, logId)
}
TYPE_URL_SCHEME -> {
val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java)
UrlSchemeUtils.sendMsg(settingVo, msgInfo, rule, logId)
}
else -> {
updateLogs(logId, 0, "未知发送通道")
}

@ -0,0 +1,77 @@
package com.idormy.sms.forwarder.utils.sender
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.util.Log
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.UrlSchemeSetting
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.app.AppUtils
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util.*
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER")
class UrlSchemeUtils private constructor() {
companion object {
private val TAG: String = UrlSchemeUtils::class.java.simpleName
fun sendMsg(
setting: UrlSchemeSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
) {
val from: String = msgInfo.from
val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
} else {
msgInfo.getContentForSend(SettingUtils.smsTemplate)
}
val timestamp = System.currentTimeMillis()
val orgContent: String = msgInfo.content
val deviceMark: String = SettingUtils.extraDeviceMark
val appVersion: String = AppUtils.getAppVersionName()
val simInfo: String = msgInfo.simInfo
@SuppressLint("SimpleDateFormat") val receiveTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) //smsVo.getDate()
var urlScheme = setting.urlScheme
Log.i(TAG, "urlScheme:$urlScheme")
urlScheme = urlScheme.replace("[from]", URLEncoder.encode(from, "UTF-8"))
.replace("[content]", URLEncoder.encode(content, "UTF-8"))
.replace("[msg]", URLEncoder.encode(content, "UTF-8"))
.replace("[org_content]", URLEncoder.encode(orgContent, "UTF-8"))
.replace("[device_mark]", URLEncoder.encode(deviceMark, "UTF-8"))
.replace("[app_version]", URLEncoder.encode(appVersion, "UTF-8"))
.replace("[title]", URLEncoder.encode(simInfo, "UTF-8"))
.replace("[card_slot]", URLEncoder.encode(simInfo, "UTF-8"))
.replace("[receive_time]", URLEncoder.encode(receiveTime, "UTF-8"))
.replace("[timestamp]", timestamp.toString())
.replace("\n", "%0A")
Log.i(TAG, "urlScheme:$urlScheme")
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlScheme))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
XUtil.getContext().startActivity(intent)
SendUtils.updateLogs(logId, 2, "调用成功")
} catch (e: Exception) {
e.printStackTrace()
Log.e(TAG, e.message.toString())
SendUtils.updateLogs(logId, 0, e.message.toString())
}
}
fun sendMsg(setting: UrlSchemeSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -0,0 +1,122 @@
<?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:background="?attr/xui_config_color_background"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical">
<LinearLayout
style="@style/senderBarStyleWithSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sender_name_status"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_clearButton="true" />
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true" />
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/url_scheme"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/url_scheme_tips"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_url_scheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
app:met_clearButton="true" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp">
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_del"
style="@style/SuperButton.Gray.Icon"
android:drawableStart="@drawable/icon_delete"
android:paddingStart="15dp"
android:text="@string/del"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_save"
style="@style/SuperButton.Blue.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_save"
android:paddingStart="15dp"
android:text="@string/save"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_test"
style="@style/SuperButton.Green.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_test"
android:paddingStart="15dp"
android:text="@string/test"
android:textSize="11sp"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</LinearLayout>

@ -277,6 +277,7 @@
<string name="invalid_openid">Multiple openids are separated by ,</string>
<string name="invalid_webserver">WebServer is empty or not a valid URL</string>
<string name="invalid_webhook">WebHook is empty or not a valid URL</string>
<string name="invalid_url_scheme">URL Scheme is empty or invalid</string>
<string name="invalid_at_mobiles">toUser/toParty/toTag cannot be empty or select @all</string>
<string name="invalid_wework_agent">CoreID, AgentID, and Secret cannot be empty</string>
<string name="invalid_dingtalk_inner_robot">AgentId, AppKey, AppSecret, and UserIds cannot be empty</string>
@ -334,12 +335,12 @@
<!--SettingActivity-->
<string name="notify_content">Notify Content</string>
<string name="device_name">Device Name</string>
<string name="sim_sub_id">SIM1 SubId</string>
<string name="sim_sub_id">SIM SubId</string>
<string name="sim1_remark" tools:ignore="Typos">SIM1 SubId/Label</string>
<string name="sim2_remark" tools:ignore="Typos">SIM2 SubId/Label</string>
<string name="carrier_mobile" tools:ignore="Typos">Label of SIM,\neg. AT&amp;T_88888888</string>
<string name="tip_number_only_error_message">Number must be greater than 0!</string>
<string name="regexp_number_only">^[1-9]?\\d+$</string>
<string name="regexp_number_only" tools:ignore="Typos">^[1-9]?\\d+$</string>
<string name="low_power_alarm_threshold">Low Power Alarm</string>
<string name="low_power_alarm_threshold_tips">Value range: 099.\nLeft blank or 0 is disabled</string>
<string name="retry_interval">Retry Interval</string>
@ -695,6 +696,9 @@
<string name="wework_webHook">WebHook</string>
<string name="wework_webHook_tips">Example: https://qyapi.weixin.qq.com/cgixx?key=xxx</string>
<string name="url_scheme">URL Scheme</string>
<string name="url_scheme_tips">Examplemyapp://api/add?&amp;type=0&amp;msg=[msg]</string>
<string name="webhook_server">Webhook Server</string>
<string name="webhook_server_tips">For example: https://a.b.com/msg?token=xyz</string>
<string name="webhook_params">Params</string>

@ -278,6 +278,7 @@
<string name="invalid_openid">多个 openid 用 , 隔开</string>
<string name="invalid_webserver">WebServer为空 或 不是有效URL</string>
<string name="invalid_webhook">WebHook为空 或 不是有效URL</string>
<string name="invalid_url_scheme">URL Scheme 为空 或 无效</string>
<string name="invalid_at_mobiles">指定成员/指定部门/指定标签 不能为空 或者 选择@all</string>
<string name="invalid_wework_agent">企业ID、AgentID、Secret都不能为空</string>
<string name="invalid_dingtalk_inner_robot">AgentId、AppKey、AppSecret、UserIds都不能为空</string>
@ -340,7 +341,7 @@
<string name="sim2_remark" tools:ignore="Typos">SIM2主键/备注</string>
<string name="carrier_mobile">序号/运营商_手机号</string>
<string name="tip_number_only_error_message">数字必须大于0!</string>
<string name="regexp_number_only">^[1-9]?\\d+$</string>
<string name="regexp_number_only" tools:ignore="Typos">^[1-9]?\\d+$</string>
<string name="low_power_alarm_threshold">安全电量范围(%)</string>
<string name="low_power_alarm_threshold_tips">超出安全范围将发出预警</string>
<string name="retry_interval">请求重试机制</string>
@ -696,6 +697,9 @@
<string name="wework_webHook">WebHook地址</string>
<string name="wework_webHook_tips">示例https://qyapi.weixin.qq.com/cgixx?key=xxx</string>
<string name="url_scheme">URL Scheme</string>
<string name="url_scheme_tips">示例myapp://api/add?&amp;type=0&amp;msg=[msg]</string>
<string name="webhook_server">Webhook Server</string>
<string name="webhook_server_tips">例如https://a.b.com/msg?token=xyz</string>
<string name="webhook_params">Params</string>

Loading…
Cancel
Save