From 7bc7bfa5141de0088ff40c7f4ad00c5c01bbe8a9 Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Thu, 9 Feb 2023 18:07:57 +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=9F=A5=E8=AF=A2=E6=89=8B=E6=9C=BA=E5=AE=9A=E4=BD=8D=EF=BC=88?= =?UTF-8?q?=E6=96=B9=E4=BE=BF=E6=89=BE=E5=9B=9E=E6=89=8B=E6=9C=BA/?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E8=80=81=E5=B0=91=E8=B5=B0=E4=B8=A2=EF=BC=89?= =?UTF-8?q?=20#256?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 5 +- app/src/main/AndroidManifest.xml | 3 + .../sms/forwarder/entity/LocationInfo.kt | 25 +++ .../sms/forwarder/fragment/ClientFragment.kt | 6 +- .../sms/forwarder/fragment/ServerFragment.kt | 106 +++++---- .../fragment/client/LocationFragment.kt | 186 ++++++++++++++++ .../server/controller/ConfigController.kt | 1 + .../server/controller/LocationController.kt | 28 +++ .../sms/forwarder/server/model/ConfigData.kt | 68 +++--- .../sms/forwarder/service/HttpService.kt | 210 ++++++++++++------ .../idormy/sms/forwarder/utils/Constants.kt | 9 + .../sms/forwarder/utils/HttpServerUtils.kt | 7 + .../main/res/drawable/icon_api_location.webp | Bin 0 -> 5800 bytes app/src/main/res/drawable/icon_pushdeer.webp | Bin 3062 -> 0 bytes .../res/layout/fragment_client_location.xml | 58 +++++ app/src/main/res/layout/fragment_server.xml | 35 +++ app/src/main/res/values-en/strings.xml | 7 + app/src/main/res/values/strings.xml | 7 + build.gradle | 1 - 19 files changed, 616 insertions(+), 146 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/entity/LocationInfo.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/fragment/client/LocationFragment.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/server/controller/LocationController.kt create mode 100644 app/src/main/res/drawable/icon_api_location.webp delete mode 100644 app/src/main/res/drawable/icon_pushdeer.webp create mode 100644 app/src/main/res/layout/fragment_client_location.xml diff --git a/app/build.gradle b/app/build.gradle index 9842b3d8..dac863ec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -265,10 +265,11 @@ dependencies { //国密算法SM4 的JAVA实现(基于BC实现) api 'org.bouncycastle:bcprov-jdk15on:1.70' + + //Location 是一个通过 Android 自带的 LocationManager 来实现的定位功能:https://github.com/jenly1314/Location + implementation 'com.github.pppscn:location:1.0.0' } //自动添加X-Library依赖 apply from: 'x-library.gradle' //walle多渠道打包 //apply from: 'multiple-channel.gradle' - - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 004f5d80..99c91301 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -54,6 +54,9 @@ + + + (), 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))) { + 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))) { XToastUtils.error(getString(R.string.disabled_on_the_server)) return } - @Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(item.classPath) as Class) //跳转的fragment - .setNewActivity(true).open(this) + @Suppress("UNCHECKED_CAST") + PageOption.to(Class.forName(item.classPath) as Class).setNewActivity(true).open(this) } catch (e: Exception) { e.printStackTrace() XToastUtils.error(e.message.toString()) 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 0fc0ca4c..e28aae7b 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 @@ -6,6 +6,7 @@ import android.os.Handler import android.os.Looper import android.text.Editable import android.text.TextWatcher +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -224,6 +225,18 @@ class ServerFragment : BaseFragment(), View.OnClickListe HttpServerUtils.enableApiWol = isChecked } + binding!!.sbApiLocation.isChecked = HttpServerUtils.enableApiLocation + binding!!.sbApiLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + HttpServerUtils.enableApiLocation = isChecked + if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) { + Log.d("ServerFragment", "onClick: 重启服务") + appContext?.stopService(Intent(appContext, HttpService::class.java)) + Thread.sleep(500) + appContext?.startService(Intent(appContext, HttpService::class.java)) + refreshButtonText() + } + } + } @SingleClick @@ -235,6 +248,7 @@ class ServerFragment : BaseFragment(), View.OnClickListe checkReadSmsPermission() checkCallPermission() checkContactsPermission() + checkLocationPermission() if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) { appContext?.stopService(Intent(appContext, HttpService::class.java)) } else { @@ -292,28 +306,21 @@ class ServerFragment : BaseFragment(), View.OnClickListe XToastUtils.error(String.format(getString(R.string.download_first), downloadPath)) return } - MaterialDialog.Builder(requireContext()) - .title(getString(R.string.select_web_client_directory)) - .content(String.format(getString(R.string.root_directory), downloadPath)) - .items(dirList) - .itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence -> - val webPath = "$downloadPath/$text" - binding!!.etWebPath.setText(webPath) - HttpServerUtils.serverWebPath = webPath + MaterialDialog.Builder(requireContext()).title(getString(R.string.select_web_client_directory)).content(String.format(getString(R.string.root_directory), downloadPath)).items(dirList).itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence -> + val webPath = "$downloadPath/$text" + binding!!.etWebPath.setText(webPath) + HttpServerUtils.serverWebPath = webPath - XToastUtils.info(getString(R.string.restarting_httpserver)) - if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) { - appContext?.stopService(Intent(appContext, HttpService::class.java)) - appContext?.startService(Intent(appContext, HttpService::class.java)) - } else { - appContext?.startService(Intent(appContext, HttpService::class.java)) - } - refreshButtonText() - true // allow selection + XToastUtils.info(getString(R.string.restarting_httpserver)) + if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) { + appContext?.stopService(Intent(appContext, HttpService::class.java)) + appContext?.startService(Intent(appContext, HttpService::class.java)) + } else { + appContext?.startService(Intent(appContext, HttpService::class.java)) } - .positiveText(R.string.select) - .negativeText(R.string.cancel) - .show() + refreshButtonText() + true // allow selection + }.positiveText(R.string.select).negativeText(R.string.cancel).show() } else -> {} } @@ -342,8 +349,7 @@ class ServerFragment : BaseFragment(), View.OnClickListe private fun checkSendSmsPermission() { XXPermissions.with(this) // 发送短信 - .permission(Permission.SEND_SMS) - .request(object : OnPermissionCallback { + .permission(Permission.SEND_SMS).request(object : OnPermissionCallback { override fun onGranted(permissions: List, all: Boolean) { } @@ -369,8 +375,7 @@ class ServerFragment : BaseFragment(), View.OnClickListe // 发送短信 .permission(Permission.SEND_SMS) // 读取短信 - .permission(Permission.READ_SMS) - .request(object : OnPermissionCallback { + .permission(Permission.READ_SMS).request(object : OnPermissionCallback { override fun onGranted(permissions: List, all: Boolean) { } @@ -396,8 +401,7 @@ class ServerFragment : BaseFragment(), View.OnClickListe // 读取手机号码 .permission(Permission.READ_PHONE_NUMBERS) // 读取通话记录 - .permission(Permission.READ_CALL_LOG) - .request(object : OnPermissionCallback { + .permission(Permission.READ_CALL_LOG).request(object : OnPermissionCallback { override fun onGranted(permissions: List, all: Boolean) { } @@ -417,24 +421,42 @@ class ServerFragment : BaseFragment(), View.OnClickListe //联系人权限 private fun checkContactsPermission() { - XXPermissions.with(this) - .permission(*Permission.Group.CONTACTS) - .request(object : OnPermissionCallback { - override fun onGranted(permissions: List, all: Boolean) { - } + XXPermissions.with(this).permission(*Permission.Group.CONTACTS).request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + } - override fun onDenied(permissions: List, never: Boolean) { - if (never) { - XToastUtils.error(R.string.toast_denied_never) - // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(requireContext(), permissions) - } else { - XToastUtils.error(R.string.toast_denied) - } - HttpServerUtils.enableApiContactQuery = false - binding!!.sbApiQueryContacts.isChecked = false + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) } - }) + HttpServerUtils.enableApiContactQuery = false + binding!!.sbApiQueryContacts.isChecked = false + } + }) + } + + //联系人权限 + private fun checkLocationPermission() { + XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) + } + HttpServerUtils.enableApiLocation = false + binding!!.sbApiLocation.isChecked = false + } + }) } override fun onDestroy() { diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/LocationFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/LocationFragment.kt new file mode 100644 index 00000000..2d1c5ab5 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/LocationFragment.kt @@ -0,0 +1,186 @@ +package com.idormy.sms.forwarder.fragment.client + +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.FragmentClientLocationBinding +import com.idormy.sms.forwarder.entity.LocationInfo +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.utils.* +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.xui.widget.grouplist.XUIGroupListView +import com.xuexiang.xutil.data.ConvertTools + +@Suppress("PropertyName") +@Page(name = "远程找手机") +class LocationFragment : BaseFragment(), View.OnClickListener { + + val TAG: String = LocationFragment::class.java.simpleName + private var mCountDownHelper: CountDownButtonHelper? = null + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientLocationBinding { + return FragmentClientLocationBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + return super.initTitle()!!.setImmersive(false).setTitle(R.string.api_location) + } + + /** + * 初始化控件 + */ + override fun initViews() { + //发送按钮增加倒计时,避免重复点击 + mCountDownHelper = CountDownButtonHelper(binding!!.btnRefresh, SettingUtils.requestTimeout) + mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener { + override fun onCountDown(time: Int) { + binding!!.btnRefresh.text = String.format(getString(R.string.seconds_n), time) + } + + override fun onFinished() { + binding!!.btnRefresh.text = getString(R.string.refresh) + } + }) + + getLocation() + } + + override fun initListeners() { + binding!!.btnRefresh.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + when (v.id) { + R.id.btn_refresh -> { + getLocation() + } + else -> {} + } + } + + private fun getLocation() { + val requestUrl: String = HttpServerUtils.serverAddress + "/location/query" + 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 dataMap: MutableMap = mutableMapOf() + 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 -> { + try { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey) + 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)) + mCountDownHelper?.finish() + + val locationInfo = resp.data ?: return + + val groupListView = binding!!.infoList + groupListView.removeAllViews() + val section = XUIGroupListView.newSection(context) + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.location_longitude), locationInfo.longitude))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.location_latitude), locationInfo.latitude))) {} + if (locationInfo.address != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.location_address), locationInfo.address))) {} + if (locationInfo.time != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.location_time), locationInfo.time))) {} + if (locationInfo.provider != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.location_provider), locationInfo.provider))) {} + section.addTo(groupListView) + } 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() + } + }) + } + + 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/controller/ConfigController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt index 685b9af8..1023d1bb 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 @@ -37,6 +37,7 @@ class ConfigController { HttpServerUtils.enableApiContactQuery, HttpServerUtils.enableApiBatteryQuery, HttpServerUtils.enableApiWol, + HttpServerUtils.enableApiLocation, SettingUtils.extraDeviceMark, SettingUtils.extraSim1, SettingUtils.extraSim2, diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/LocationController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/LocationController.kt new file mode 100644 index 00000000..dfbeba0b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/LocationController.kt @@ -0,0 +1,28 @@ +package com.idormy.sms.forwarder.server.controller + +import android.annotation.SuppressLint +import android.util.Log +import com.idormy.sms.forwarder.entity.LocationInfo +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.server.model.EmptyData +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.yanzhenjie.andserver.annotation.* +import java.util.* + +@SuppressLint("SimpleDateFormat") +@Suppress("PrivatePropertyName", "DEPRECATION") +@RestController +@RequestMapping(path = ["/location"]) +class LocationController { + + private val TAG: String = LocationController::class.java.simpleName + + //远程找手机 + @CrossOrigin(methods = [RequestMethod.POST]) + @PostMapping("/query") + fun query(@RequestBody bean: BaseRequest): LocationInfo { + Log.d(TAG, bean.data.toString()) + return HttpServerUtils.apiLocationCache + } + +} \ 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 38ad96fd..67ead672 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 @@ -1,34 +1,36 @@ -package com.idormy.sms.forwarder.server.model - -import com.google.gson.annotations.SerializedName -import com.idormy.sms.forwarder.entity.SimInfo -import java.io.Serializable - -data class ConfigData( - @SerializedName("enable_api_clone") - var enableApiClone: Boolean = false, - @SerializedName("enable_api_sms_send") - var enableApiSmsSend: Boolean = false, - @SerializedName("enable_api_sms_query") - var enableApiSmsQuery: Boolean = false, - @SerializedName("enable_api_call_query") - var enableApiCallQuery: Boolean = false, - @SerializedName("enable_api_contact_query") - var enableApiContactQuery: Boolean = false, - @SerializedName("enable_api_battery_query") - var enableApiBatteryQuery: Boolean = false, - @SerializedName("enable_api_wol") - var enableApiWol: Boolean = false, - @SerializedName("extra_device_mark") - var extraDeviceMark: String = "", - @SerializedName("extra_sim1") - var extraSim1: String = "", - @SerializedName("extra_sim2") - var extraSim2: String = "", - @SerializedName("sim_info_list") - var simInfoList: MutableMap = mutableMapOf(), - @SerializedName("version_code") - var versionCode: Int = 0, - @SerializedName("version_name") - var versionName: String = "", +package com.idormy.sms.forwarder.server.model + +import com.google.gson.annotations.SerializedName +import com.idormy.sms.forwarder.entity.SimInfo +import java.io.Serializable + +data class ConfigData( + @SerializedName("enable_api_clone") + var enableApiClone: Boolean = false, + @SerializedName("enable_api_sms_send") + var enableApiSmsSend: Boolean = false, + @SerializedName("enable_api_sms_query") + var enableApiSmsQuery: Boolean = false, + @SerializedName("enable_api_call_query") + var enableApiCallQuery: Boolean = false, + @SerializedName("enable_api_contact_query") + var enableApiContactQuery: Boolean = false, + @SerializedName("enable_api_battery_query") + var enableApiBatteryQuery: Boolean = false, + @SerializedName("enable_api_wol") + var enableApiWol: Boolean = false, + @SerializedName("enable_api_location") + var enableApiLocation: Boolean = false, + @SerializedName("extra_device_mark") + var extraDeviceMark: String = "", + @SerializedName("extra_sim1") + var extraSim1: String = "", + @SerializedName("extra_sim2") + var extraSim2: String = "", + @SerializedName("sim_info_list") + var simInfoList: MutableMap = mutableMapOf(), + @SerializedName("version_code") + var versionCode: Int = 0, + @SerializedName("version_name") + var versionName: String = "", ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/HttpService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/HttpService.kt index 942003b5..c064d28d 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/service/HttpService.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/service/HttpService.kt @@ -1,66 +1,146 @@ -package com.idormy.sms.forwarder.service - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import android.util.Log -import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT -import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT -import com.idormy.sms.forwarder.utils.SettingUtils -import com.yanzhenjie.andserver.AndServer -import com.yanzhenjie.andserver.Server -import java.util.concurrent.TimeUnit - -@Suppress("PrivatePropertyName") -class HttpService : Service(), Server.ServerListener { - - private val TAG: String = "HttpService" - private val server by lazy { - AndServer.webServer(this) - .port(HTTP_SERVER_PORT) - .listener(this) - .timeout(HTTP_SERVER_TIME_OUT, TimeUnit.SECONDS) - .build() - } - - override fun onBind(p0: Intent?): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - - //纯客户端模式 - if (SettingUtils.enablePureClientMode) return - - Log.i(TAG, "onCreate: ") - server.startup() - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i(TAG, "onStartCommand: ") - return super.onStartCommand(intent, flags, startId) - } - - override fun onDestroy() { - super.onDestroy() - - //纯客户端模式 - if (SettingUtils.enablePureClientMode) return - - Log.i(TAG, "onDestroy: ") - server.shutdown() - } - - override fun onException(e: Exception?) { - Log.i(TAG, "onException: ") - } - - override fun onStarted() { - Log.i(TAG, "onStarted: ") - } - - override fun onStopped() { - Log.i(TAG, "onStopped: ") - } +package com.idormy.sms.forwarder.service + +import android.annotation.SuppressLint +import android.app.Service +import android.content.Intent +import android.location.Criteria +import android.location.Geocoder +import android.location.Location +import android.os.IBinder +import android.util.Log +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.entity.LocationInfo +import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT +import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.king.location.LocationClient +import com.king.location.LocationErrorCode +import com.king.location.OnExceptionListener +import com.king.location.OnLocationListener +import com.yanzhenjie.andserver.AndServer +import com.yanzhenjie.andserver.Server +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit + +@SuppressLint("SimpleDateFormat") +@Suppress("PrivatePropertyName", "DEPRECATION") +class HttpService : Service(), Server.ServerListener { + + private val TAG: String = "HttpService" + private val server by lazy { + AndServer.webServer(this).port(HTTP_SERVER_PORT).listener(this).timeout(HTTP_SERVER_TIME_OUT, TimeUnit.SECONDS).build() + } + private val locationClient by lazy { LocationClient(App.context) } + private val geocoder by lazy { Geocoder(App.context) } + private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss") } + + override fun onBind(p0: Intent?): IBinder? { + return null + } + + override fun onCreate() { + super.onCreate() + + //纯客户端模式 + if (SettingUtils.enablePureClientMode) return + + Log.i(TAG, "onCreate: ") + server.startup() + + //远程找手机 + if (HttpServerUtils.enableApiLocation) { + //可根据具体需求设置定位配置参数(这里只列出一些主要的参数) + val locationOption = locationClient.getLocationOption().setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度 + .setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗 + .setMinTime(10000)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔:10000毫秒,最小间隔:1000毫秒 + .setMinDistance(0)//设置位置更新最小距离(单位:米);默认距离:0米 + .setOnceLocation(false)//设置是否只定位一次,默认为 false,当设置为 true 时,则只定位一次后,会自动停止定位 + .setLastKnownLocation(true)//设置是否获取最后一次缓存的已知位置,默认为 true + //设置定位配置参数 + locationClient.setLocationOption(locationOption) + locationClient.startLocation() + + //设置定位监听 + locationClient.setOnLocationListener(object : OnLocationListener() { + override fun onLocationChanged(location: Location) { + //位置信息 + Log.d(TAG, "onLocationChanged(location = ${location})") + + val locationInfo = LocationInfo( + location.longitude, + location.latitude, + "", + simpleDateFormat.format(Date(location.time)), + location.provider.toString() + ) + + //根据坐标经纬度获取位置地址信息(WGS-84坐标系) + val list = geocoder.getFromLocation(location.latitude, location.longitude, 1) + if (list?.isNotEmpty() == true) { + locationInfo.address = list[0].getAddressLine(0) + } + + Log.d(TAG, "locationInfo = $locationInfo") + HttpServerUtils.apiLocationCache = locationInfo + } + + override fun onProviderEnabled(provider: String) { + super.onProviderEnabled(provider) + Log.d(TAG, "onProviderEnabled(provider = ${provider})") + } + + override fun onProviderDisabled(provider: String) { + super.onProviderDisabled(provider) + Log.d(TAG, "onProviderDisabled(provider = ${provider})") + } + + }) + + //设置异常监听 + locationClient.setOnExceptionListener(object : OnExceptionListener { + override fun onException(@LocationErrorCode errorCode: Int, e: Exception) { + //定位出现异常 + Log.w(TAG, "onException(errorCode = ${errorCode}, e = ${e})") + } + }) + + if (locationClient.isStarted()) {//如果已经开始定位,则先停止定位 + locationClient.stopLocation() + } + locationClient.startLocation() + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.i(TAG, "onStartCommand: ") + return super.onStartCommand(intent, flags, startId) + } + + override fun onDestroy() { + super.onDestroy() + + //纯客户端模式 + if (SettingUtils.enablePureClientMode) return + + Log.i(TAG, "onDestroy: ") + server.shutdown() + + if (HttpServerUtils.enableApiLocation && locationClient.isStarted()) {//如果已经开始定位,则先停止定位 + locationClient.stopLocation() + } + } + + override fun onException(e: Exception?) { + Log.i(TAG, "onException: ") + } + + override fun onStarted() { + Log.i(TAG, "onStarted: ") + } + + override fun onStopped() { + Log.i(TAG, "onStopped: ") + } } \ No newline at end of file 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 2601e151..cf668926 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 @@ -357,6 +357,8 @@ 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_BATTERY_QUERY = "enable_api_battery_query" const val SP_ENABLE_API_WOL = "enable_api_wol" +const val SP_ENABLE_API_LOCATION = "enable_api_location" +const val SP_API_LOCATION_CACHE = "api_location_cache" const val SP_WOL_HISTORY = "wol_history" const val SP_SERVER_ADDRESS = "server_address" const val SP_SERVER_HISTORY = "server_history" @@ -414,4 +416,11 @@ var CLIENT_FRAGMENT_LIST = listOf( CoreAnim.slide, R.drawable.icon_api_wol ), + PageInfo( + getString(R.string.api_location), + "com.idormy.sms.forwarder.fragment.client.LocationFragment", + "{\"\":\"\"}", + CoreAnim.slide, + R.drawable.icon_api_location + ), ) \ 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 72de5370..9d43b630 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 @@ -8,6 +8,7 @@ import com.google.gson.Gson import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.entity.CloneInfo +import com.idormy.sms.forwarder.entity.LocationInfo import com.idormy.sms.forwarder.server.model.BaseRequest import com.xuexiang.xui.utils.ResUtils.getString import com.xuexiang.xutil.app.AppUtils @@ -84,6 +85,12 @@ class HttpServerUtils private constructor() { //是否启用远程WOL var enableApiWol: Boolean by SharedPreference(SP_ENABLE_API_WOL, true) + //是否启用远程找手机 + var enableApiLocation: Boolean by SharedPreference(SP_ENABLE_API_LOCATION, true) + + //远程找手机定位缓存 + var apiLocationCache: LocationInfo by SharedPreference(SP_API_LOCATION_CACHE, LocationInfo()) + //WOL历史记录 var wolHistory: String by SharedPreference(SP_WOL_HISTORY, "") diff --git a/app/src/main/res/drawable/icon_api_location.webp b/app/src/main/res/drawable/icon_api_location.webp new file mode 100644 index 0000000000000000000000000000000000000000..1cf7e1a35b1407fd6357b326cc9365d36696ce0b GIT binary patch literal 5800 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/icon_pushdeer.webp b/app/src/main/res/drawable/icon_pushdeer.webp deleted file mode 100644 index d718ef5ca5d6b5f6776e9549974f14ab85d6faff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3062 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 E0N<(uXaE2J diff --git a/app/src/main/res/layout/fragment_client_location.xml b/app/src/main/res/layout/fragment_client_location.xml new file mode 100644 index 00000000..816e0f8e --- /dev/null +++ b/app/src/main/res/layout/fragment_client_location.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + \ 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 e259227e..d4566d0d 100644 --- a/app/src/main/res/layout/fragment_server.xml +++ b/app/src/main/res/layout/fragment_server.xml @@ -732,6 +732,41 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index c8f3391d..bffb4374 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -808,6 +808,13 @@ Remotely query mobile phone power and battery status Remotely WOL Turn on your Wake-On-LAN enabled devices remotely + Location + Remote query mobile phone location + Longitude:%s + Latitude:%s + Address:%s + Time:%s + Provider:%s Sim Slot Phone Numbers diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 563dc088..7bfde21c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -809,6 +809,13 @@ 远程查询手机电量与电池状态 远程WOL 远程打开启用LAN唤醒功能(Wake-On-LAN)的设备 + 远程找手机 + 远程查询手机定位,方便找回手机/防止老少走丢 + 经度:%s + 维度:%s + 地址:%s + 时间:%s + 供应商:%s 发送卡槽 手机号码 diff --git a/build.gradle b/build.gradle index d22cecba..73c1bc56 100644 --- a/build.gradle +++ b/build.gradle @@ -48,4 +48,3 @@ task clean(type: Delete) { } } } -