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