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 00000000..1cf7e1a3
Binary files /dev/null and b/app/src/main/res/drawable/icon_api_location.webp differ
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 d718ef5c..00000000
Binary files a/app/src/main/res/drawable/icon_pushdeer.webp and /dev/null differ
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) {
}
}
}
-