From 1e1dd8e3fdfad91ea4bf9c9fc595f3d0013ffece Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Fri, 10 Feb 2023 15:03:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E8=BF=9C=E7=A8=8B?= =?UTF-8?q?=E6=94=B9=E8=AF=9D=E7=B0=BF=EF=BC=88=E6=96=B9=E4=BE=BF=E7=BB=99?= =?UTF-8?q?=E8=80=81=E4=BA=BA=E5=AE=B6=E6=B7=BB=E5=8A=A0=E8=81=94=E7=B3=BB?= =?UTF-8?q?=E4=BA=BA=EF=BC=89=20#256?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/forwarder/fragment/ClientFragment.kt | 2 +- .../sms/forwarder/fragment/ServerFragment.kt | 8 + .../fragment/client/ContactAddFragment.kt | 186 ++++++++++++++++++ .../server/component/LoggerInterceptor.kt | 111 ++++++----- .../server/controller/ConfigController.kt | 1 + .../server/controller/ContactController.kt | 94 ++++++--- .../sms/forwarder/server/model/ConfigData.kt | 2 + .../idormy/sms/forwarder/utils/Constants.kt | 26 ++- .../sms/forwarder/utils/HttpServerUtils.kt | 3 + .../res/drawable/icon_api_contact_add.webp | Bin 0 -> 2926 bytes .../layout/fragment_client_contact_add.xml | 96 +++++++++ app/src/main/res/layout/fragment_server.xml | 57 ++++-- app/src/main/res/values-en/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + 14 files changed, 491 insertions(+), 103 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactAddFragment.kt create mode 100644 app/src/main/res/drawable/icon_api_contact_add.webp create mode 100644 app/src/main/res/layout/fragment_client_contact_add.xml diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt index 772b992d..d4bd6656 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt @@ -259,7 +259,7 @@ class ClientFragment : BaseFragment(), View.OnClickListe XToastUtils.error(getString(R.string.click_test_button_first)) return } - if (serverConfig != null && ((item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend) || (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery) || (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery) || (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery) || (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery) || (item.name == ResUtils.getString(R.string.api_wol) && !serverConfig!!.enableApiWol) || (item.name == ResUtils.getString(R.string.api_location) && !serverConfig!!.enableApiLocation))) { + if (serverConfig != null && ((item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend) || (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery) || (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery) || (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery) || (item.name == ResUtils.getString(R.string.api_contact_add) && !serverConfig!!.enableApiContactAdd) || (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery) || (item.name == ResUtils.getString(R.string.api_wol) && !serverConfig!!.enableApiWol) || (item.name == ResUtils.getString(R.string.api_location) && !serverConfig!!.enableApiLocation))) { XToastUtils.error(getString(R.string.disabled_on_the_server)) return } diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt index e28aae7b..3904a2e6 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt @@ -215,6 +215,12 @@ class ServerFragment : BaseFragment(), View.OnClickListe if (isChecked) checkContactsPermission() } + binding!!.sbApiAddContacts.isChecked = HttpServerUtils.enableApiContactAdd + binding!!.sbApiAddContacts.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + HttpServerUtils.enableApiContactAdd = isChecked + if (isChecked) checkContactsPermission() + } + binding!!.sbApiQueryBattery.isChecked = HttpServerUtils.enableApiBatteryQuery binding!!.sbApiQueryBattery.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> HttpServerUtils.enableApiBatteryQuery = isChecked @@ -435,6 +441,8 @@ class ServerFragment : BaseFragment(), View.OnClickListe } HttpServerUtils.enableApiContactQuery = false binding!!.sbApiQueryContacts.isChecked = false + HttpServerUtils.enableApiContactAdd = false + binding!!.sbApiAddContacts.isChecked = false } }) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactAddFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactAddFragment.kt new file mode 100644 index 00000000..11a2f7ec --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactAddFragment.kt @@ -0,0 +1,186 @@ +package com.idormy.sms.forwarder.fragment.client + +import android.annotation.SuppressLint +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentClientContactAddBinding +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.utils.CountDownButtonHelper +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xutil.data.ConvertTools + +@Suppress("PropertyName") +@Page(name = "远程加话簿") +class ContactAddFragment : BaseFragment(), View.OnClickListener { + + val TAG: String = ContactAddFragment::class.java.simpleName + private var mCountDownHelper: CountDownButtonHelper? = null + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientContactAddBinding { + return FragmentClientContactAddBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + return super.initTitle()!!.setImmersive(false).setTitle(R.string.api_contact_add) + } + + /** + * 初始化控件 + */ + @SuppressLint("SetTextI18n") + override fun initViews() { + //发送按钮增加倒计时,避免重复点击 + mCountDownHelper = CountDownButtonHelper(binding!!.btnSubmit, SettingUtils.requestTimeout) + mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener { + override fun onCountDown(time: Int) { + binding!!.btnSubmit.text = String.format(getString(R.string.seconds_n), time) + } + + override fun onFinished() { + binding!!.btnSubmit.text = getString(R.string.submit) + } + }) + } + + override fun initListeners() { + binding!!.btnSubmit.setOnClickListener(this) + LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS, String::class.java).observeSticky(this) { value: String -> + binding!!.etPhoneNumbers.setText(value) + } + } + + @SingleClick + override fun onClick(v: View) { + when (v.id) { + R.id.btn_submit -> { + val requestUrl: String = HttpServerUtils.serverAddress + "/contact/add" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey) + } + + val phoneNumbers = binding!!.etPhoneNumbers.text.toString() + val phoneRegex = getString(R.string.phone_numbers_regex).toRegex() + if (!phoneRegex.matches(phoneNumbers)) { + XToastUtils.error(ResUtils.getString(R.string.phone_numbers_error)) + return + } + + val name = binding!!.etDisplayName.text.toString() + + val dataMap: MutableMap = mutableMapOf() + dataMap["phone_number"] = phoneNumbers + dataMap["name"] = name + msgMap["data"] = dataMap + + var requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + val postRequest = XHttp.post(requestUrl) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .timeStamp(true) + + when (HttpServerUtils.clientSafetyMeasures) { + 2 -> { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } + 3 -> { + try { + val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey) + //requestMsg = Base64.encode(requestMsg.toByteArray()) + val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) + requestMsg = ConvertTools.bytes2HexString(encryptCBC) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } + else -> { + postRequest.upJson(requestMsg) + } + } + + mCountDownHelper?.start() + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + mCountDownHelper?.finish() + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) + } else if (HttpServerUtils.clientSafetyMeasures == 3) { + val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey) + val encryptCBC = ConvertTools.hexStringToByteArray(json) + val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) + json = String(decryptCBC) + } + val resp: BaseResponse = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + mCountDownHelper?.finish() + } + }) + } + else -> {} + } + } + + override fun onDestroyView() { + if (mCountDownHelper != null) mCountDownHelper!!.recycle() + super.onDestroyView() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/LoggerInterceptor.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/LoggerInterceptor.kt index 69b836db..77e640c7 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/component/LoggerInterceptor.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/LoggerInterceptor.kt @@ -1,55 +1,58 @@ -package com.idormy.sms.forwarder.server.component - -import android.util.Log -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.utils.HttpServerUtils -import com.xuexiang.xui.utils.ResUtils.getString -import com.yanzhenjie.andserver.annotation.Interceptor -import com.yanzhenjie.andserver.error.HttpException -import com.yanzhenjie.andserver.framework.HandlerInterceptor -import com.yanzhenjie.andserver.framework.handler.MethodHandler -import com.yanzhenjie.andserver.framework.handler.RequestHandler -import com.yanzhenjie.andserver.http.HttpMethod -import com.yanzhenjie.andserver.http.HttpRequest -import com.yanzhenjie.andserver.http.HttpResponse - -@Suppress("PrivatePropertyName") -@Interceptor -class LoggerInterceptor : HandlerInterceptor { - - private val TAG: String = "LoggerInterceptor" - - override fun onIntercept( - request: HttpRequest, - respons: HttpResponse, - handler: RequestHandler, - ): Boolean { - if (handler is MethodHandler) { - val httpPath = request.path - val method: HttpMethod = request.method - val valueMap = request.parameter - Log.i(TAG, "Path: $httpPath") - Log.i(TAG, "Method: " + method.value()) - Log.i(TAG, "Param: $valueMap") - - //判断是否开启该功能 - if ( - (httpPath.startsWith("/clone") && !HttpServerUtils.enableApiClone) - || (httpPath.startsWith("/sms/send") && !HttpServerUtils.enableApiSmsSend) - || (httpPath.startsWith("/sms/query") && !HttpServerUtils.enableApiSmsQuery) - || (httpPath.startsWith("/call/query") && !HttpServerUtils.enableApiCallQuery) - || (httpPath.startsWith("/contact/query") && !HttpServerUtils.enableApiContactQuery) - || (httpPath.startsWith("/battery/query") && !HttpServerUtils.enableApiBatteryQuery) - ) { - throw HttpException(500, getString(R.string.disabled_on_the_server)) - } - - /* - //注意:这里读取body会导致 MessageConverter 报错:RequestBody is missing. - val body = request.body?.string() - Log.i(TAG, "Body: $body") - */ - } - return false - } +package com.idormy.sms.forwarder.server.component + +import android.util.Log +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.xuexiang.xui.utils.ResUtils.getString +import com.yanzhenjie.andserver.annotation.Interceptor +import com.yanzhenjie.andserver.error.HttpException +import com.yanzhenjie.andserver.framework.HandlerInterceptor +import com.yanzhenjie.andserver.framework.handler.MethodHandler +import com.yanzhenjie.andserver.framework.handler.RequestHandler +import com.yanzhenjie.andserver.http.HttpMethod +import com.yanzhenjie.andserver.http.HttpRequest +import com.yanzhenjie.andserver.http.HttpResponse + +@Suppress("PrivatePropertyName") +@Interceptor +class LoggerInterceptor : HandlerInterceptor { + + private val TAG: String = "LoggerInterceptor" + + override fun onIntercept( + request: HttpRequest, + respons: HttpResponse, + handler: RequestHandler, + ): Boolean { + if (handler is MethodHandler) { + val httpPath = request.path + val method: HttpMethod = request.method + val valueMap = request.parameter + Log.i(TAG, "Path: $httpPath") + Log.i(TAG, "Method: " + method.value()) + Log.i(TAG, "Param: $valueMap") + + //判断是否开启该功能 + if ( + (httpPath.startsWith("/clone") && !HttpServerUtils.enableApiClone) + || (httpPath.startsWith("/sms/query") && !HttpServerUtils.enableApiSmsQuery) + || (httpPath.startsWith("/sms/send") && !HttpServerUtils.enableApiSmsSend) + || (httpPath.startsWith("/call/query") && !HttpServerUtils.enableApiCallQuery) + || (httpPath.startsWith("/contact/query") && !HttpServerUtils.enableApiContactQuery) + || (httpPath.startsWith("/contact/add") && !HttpServerUtils.enableApiContactAdd) + || (httpPath.startsWith("/wol/send") && !HttpServerUtils.enableApiWol) + || (httpPath.startsWith("/location/query") && !HttpServerUtils.enableApiLocation) + || (httpPath.startsWith("/battery/query") && !HttpServerUtils.enableApiBatteryQuery) + ) { + throw HttpException(500, getString(R.string.disabled_on_the_server)) + } + + /* + //注意:这里读取body会导致 MessageConverter 报错:RequestBody is missing. + val body = request.body?.string() + Log.i(TAG, "Body: $body") + */ + } + return false + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt index 1023d1bb..130fde47 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt @@ -35,6 +35,7 @@ class ConfigController { HttpServerUtils.enableApiSmsQuery, HttpServerUtils.enableApiCallQuery, HttpServerUtils.enableApiContactQuery, + HttpServerUtils.enableApiContactAdd, HttpServerUtils.enableApiBatteryQuery, HttpServerUtils.enableApiWol, HttpServerUtils.enableApiLocation, diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/ContactController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ContactController.kt index 5d340732..868bae89 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/controller/ContactController.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ContactController.kt @@ -1,29 +1,67 @@ -package com.idormy.sms.forwarder.server.controller - -import android.util.Log -import com.idormy.sms.forwarder.entity.ContactInfo -import com.idormy.sms.forwarder.server.model.BaseRequest -import com.idormy.sms.forwarder.server.model.ContactQueryData -import com.idormy.sms.forwarder.utils.PhoneUtils -import com.yanzhenjie.andserver.annotation.* - -@Suppress("PrivatePropertyName") -@RestController -@RequestMapping(path = ["/contact"]) -class ContactController { - - private val TAG: String = ContactController::class.java.simpleName - - //远程查话簿 - @CrossOrigin(methods = [RequestMethod.POST]) - @PostMapping("/query") - fun query(@RequestBody bean: BaseRequest): MutableList { - val contactQueryData = bean.data - Log.d(TAG, contactQueryData.toString()) - - val limit = contactQueryData.pageSize - val offset = (contactQueryData.pageNum - 1) * limit - return PhoneUtils.getContactInfoList(limit, offset, contactQueryData.phoneNumber, contactQueryData.name) - } - +package com.idormy.sms.forwarder.server.controller + +import android.content.ContentUris +import android.content.ContentValues +import android.provider.ContactsContract +import android.util.Log +import com.idormy.sms.forwarder.entity.ContactInfo +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.server.model.ContactQueryData +import com.idormy.sms.forwarder.utils.PhoneUtils +import com.xuexiang.xutil.XUtil.getContentResolver +import com.yanzhenjie.andserver.annotation.* + + +@Suppress("PrivatePropertyName") +@RestController +@RequestMapping(path = ["/contact"]) +class ContactController { + + private val TAG: String = ContactController::class.java.simpleName + + //远程查话簿 + @CrossOrigin(methods = [RequestMethod.POST]) + @PostMapping("/query") + fun query(@RequestBody bean: BaseRequest): MutableList { + val contactQueryData = bean.data + Log.d(TAG, contactQueryData.toString()) + + val limit = contactQueryData.pageSize + val offset = (contactQueryData.pageNum - 1) * limit + return PhoneUtils.getContactInfoList(limit, offset, contactQueryData.phoneNumber, contactQueryData.name) + } + + //远程加话簿 + @CrossOrigin(methods = [RequestMethod.POST]) + @PostMapping("/add") + fun add(@RequestBody bean: BaseRequest): String { + val contactData = bean.data + Log.d(TAG, contactData.toString()) + + //创建一个空的ContentValues + val values = ContentValues() + //首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId + val rawcontacturi = getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values) + val rawcontactid = ContentUris.parseId(rawcontacturi!!) + + //插入姓名数据 + values.clear() + values.put(ContactsContract.Data.RAW_CONTACT_ID, rawcontactid) + values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contactData.name) + getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values) + + //插入电话数据 + for (phoneNumber in contactData.phoneNumber.split(";")) { + values.clear() + values.put(ContactsContract.Data.RAW_CONTACT_ID, rawcontactid) + values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber) + values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE) + getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values) + } + + return "success" + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/ConfigData.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/ConfigData.kt index 67ead672..022ce7ab 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/model/ConfigData.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/ConfigData.kt @@ -15,6 +15,8 @@ data class ConfigData( var enableApiCallQuery: Boolean = false, @SerializedName("enable_api_contact_query") var enableApiContactQuery: Boolean = false, + @SerializedName("enable_api_contact_add") + var enableApiContactAdd: Boolean = false, @SerializedName("enable_api_battery_query") var enableApiBatteryQuery: Boolean = false, @SerializedName("enable_api_wol") diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index 75c067f5..5aa1dcef 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -356,6 +356,7 @@ const val SP_ENABLE_API_SMS_SEND = "enable_api_sms_send" const val SP_ENABLE_API_SMS_QUERY = "enable_api_sms_query" const val SP_ENABLE_API_CALL_QUERY = "enable_api_call_query" const val SP_ENABLE_API_CONTACT_QUERY = "enable_api_contact_query" +const val SP_ENABLE_API_CONTACT_ADD = "enable_api_contact_add" const val SP_ENABLE_API_BATTERY_QUERY = "enable_api_battery_query" const val SP_ENABLE_API_WOL = "enable_api_wol" const val SP_ENABLE_API_LOCATION = "enable_api_location" @@ -376,18 +377,18 @@ var CLIENT_FRAGMENT_LIST = listOf( R.drawable.icon_api_clone ), PageInfo( - getString(R.string.api_sms_send), - "com.idormy.sms.forwarder.fragment.client.SmsSendFragment", + getString(R.string.api_sms_query), + "com.idormy.sms.forwarder.fragment.client.SmsQueryFragment", "{\"\":\"\"}", CoreAnim.slide, - R.drawable.icon_api_sms_send + R.drawable.icon_api_sms_query ), PageInfo( - getString(R.string.api_sms_query), - "com.idormy.sms.forwarder.fragment.client.SmsQueryFragment", + getString(R.string.api_sms_send), + "com.idormy.sms.forwarder.fragment.client.SmsSendFragment", "{\"\":\"\"}", CoreAnim.slide, - R.drawable.icon_api_sms_query + R.drawable.icon_api_sms_send ), PageInfo( getString(R.string.api_call_query), @@ -404,11 +405,11 @@ var CLIENT_FRAGMENT_LIST = listOf( R.drawable.icon_api_contact_query ), PageInfo( - getString(R.string.api_battery_query), - "com.idormy.sms.forwarder.fragment.client.BatteryQueryFragment", + getString(R.string.api_contact_add), + "com.idormy.sms.forwarder.fragment.client.ContactAddFragment", "{\"\":\"\"}", CoreAnim.slide, - R.drawable.icon_api_battery_query + R.drawable.icon_api_contact_add ), PageInfo( getString(R.string.api_wol), @@ -424,4 +425,11 @@ var CLIENT_FRAGMENT_LIST = listOf( CoreAnim.slide, R.drawable.icon_api_location ), + PageInfo( + getString(R.string.api_battery_query), + "com.idormy.sms.forwarder.fragment.client.BatteryQueryFragment", + "{\"\":\"\"}", + CoreAnim.slide, + R.drawable.icon_api_battery_query + ), ) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt index 9d43b630..1c89b7ff 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt @@ -79,6 +79,9 @@ class HttpServerUtils private constructor() { //是否启用远程查话簿 var enableApiContactQuery: Boolean by SharedPreference(SP_ENABLE_API_CONTACT_QUERY, true) + //是否启用远程加话簿 + var enableApiContactAdd: Boolean by SharedPreference(SP_ENABLE_API_CONTACT_ADD, true) + //是否启用远程查电量 var enableApiBatteryQuery: Boolean by SharedPreference(SP_ENABLE_API_BATTERY_QUERY, true) diff --git a/app/src/main/res/drawable/icon_api_contact_add.webp b/app/src/main/res/drawable/icon_api_contact_add.webp new file mode 100644 index 0000000000000000000000000000000000000000..54c0aeec8e860ca8920dfe6341f442825d0e6d9f GIT binary patch literal 2926 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_server.xml b/app/src/main/res/layout/fragment_server.xml index d4566d0d..772bcf43 100644 --- a/app/src/main/res/layout/fragment_server.xml +++ b/app/src/main/res/layout/fragment_server.xml @@ -535,21 +535,21 @@ @@ -570,21 +570,21 @@ @@ -664,8 +664,7 @@ + android:layout_height="wrap_content"> @@ -767,6 +766,42 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index bffb4374..7a6eee8d 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -804,6 +804,8 @@ Remotely check call records, including incoming calls, outgoing calls, and missed calls Query Linkman Remotely check contact list + Add Linkman + Remotely add contact Query Battery Remotely query mobile phone power and battery status Remotely WOL @@ -817,6 +819,8 @@ Provider:%s Sim Slot + Display Name + Optional, address book display name Phone Numbers Required, separate multiple phone numbers with semicolons Invalid Phone Numbers, eg. 15888888888;19999999999 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7bfde21c..3dd75dd7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -805,6 +805,8 @@ 远程查通话记录,包括来电、去电、未接电话 远程查话簿 远程查联系人列表 + 远程加话簿 + 远程添加联系人 远程查电量 远程查询手机电量与电池状态 远程WOL @@ -818,6 +820,8 @@ 供应商:%s 发送卡槽 + 姓名 + 选填,通讯录显示名称 手机号码 必填,多个手机号用半角分号分隔 手机号码格式错误,例:15888888888;19999999999