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
-
- 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
+ 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))
}
- .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) {
}
}
}
-