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 772b992de3..d4bd66561d 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 e28aae7bb2..3904a2e685 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 0000000000..11a2f7ec6c --- /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 69b836dbcc..77e640c760 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 1023d1bbef..130fde47ff 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 5d34073250..868bae89ad 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 67ead6721c..022ce7ab64 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 75c067f569..5aa1dcef44 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 9d43b630d4..1c89b7fff8 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 0000000000..54c0aeec8e Binary files /dev/null and b/app/src/main/res/drawable/icon_api_contact_add.webp differ diff --git a/app/src/main/res/layout/fragment_client_contact_add.xml b/app/src/main/res/layout/fragment_client_contact_add.xml new file mode 100644 index 0000000000..962343d443 --- /dev/null +++ b/app/src/main/res/layout/fragment_client_contact_add.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 d4566d0d00..772bcf43d5 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 bffb4374d3..7a6eee8d81 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 7bfde21c75..3dd75dd7ad 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