mirror of
https://github.com/pppscn/SmsForwarder
synced 2025-08-03 01:17:41 +08:00
新增:远程查询手机定位(方便找回手机/防止老少走丢) #256
This commit is contained in:
parent
3f28080958
commit
7bc7bfa514
@ -265,10 +265,11 @@ dependencies {
|
|||||||
|
|
||||||
//国密算法SM4 的JAVA实现(基于BC实现)
|
//国密算法SM4 的JAVA实现(基于BC实现)
|
||||||
api 'org.bouncycastle:bcprov-jdk15on:1.70'
|
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依赖
|
//自动添加X-Library依赖
|
||||||
apply from: 'x-library.gradle'
|
apply from: 'x-library.gradle'
|
||||||
//walle多渠道打包
|
//walle多渠道打包
|
||||||
//apply from: 'multiple-channel.gradle'
|
//apply from: 'multiple-channel.gradle'
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,6 +54,9 @@
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.READ_LOGS"
|
android:name="android.permission.READ_LOGS"
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.idormy.sms.forwarder.entity
|
||||||
|
|
||||||
|
import com.idormy.sms.forwarder.R
|
||||||
|
import com.xuexiang.xui.utils.ResUtils
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class LocationInfo(
|
||||||
|
var longitude: Double = 0.0,
|
||||||
|
var latitude: Double = 0.0,
|
||||||
|
var address: String = "",
|
||||||
|
var time: String = "",
|
||||||
|
var provider: String = ""
|
||||||
|
) : Serializable {
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
var msg = ""
|
||||||
|
msg += "\n" + String.format(ResUtils.getString(R.string.location_longitude), longitude)
|
||||||
|
msg += "\n" + String.format(ResUtils.getString(R.string.location_latitude), latitude)
|
||||||
|
if (address != "") msg += "\n" + String.format(ResUtils.getString(R.string.location_address), address)
|
||||||
|
if (time != "") msg += "\n" + String.format(ResUtils.getString(R.string.location_time), time)
|
||||||
|
if (provider != "") msg += "\n" + String.format(ResUtils.getString(R.string.location_provider), provider)
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -259,12 +259,12 @@ class ClientFragment : BaseFragment<FragmentClientBinding?>(), View.OnClickListe
|
|||||||
XToastUtils.error(getString(R.string.click_test_button_first))
|
XToastUtils.error(getString(R.string.click_test_button_first))
|
||||||
return
|
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))
|
XToastUtils.error(getString(R.string.disabled_on_the_server))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(item.classPath) as Class<XPageFragment>) //跳转的fragment
|
@Suppress("UNCHECKED_CAST")
|
||||||
.setNewActivity(true).open(this)
|
PageOption.to(Class.forName(item.classPath) as Class<XPageFragment>).setNewActivity(true).open(this)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
XToastUtils.error(e.message.toString())
|
XToastUtils.error(e.message.toString())
|
||||||
|
@ -6,6 +6,7 @@ import android.os.Handler
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -224,6 +225,18 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
|
|||||||
HttpServerUtils.enableApiWol = isChecked
|
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
|
@SingleClick
|
||||||
@ -235,6 +248,7 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
|
|||||||
checkReadSmsPermission()
|
checkReadSmsPermission()
|
||||||
checkCallPermission()
|
checkCallPermission()
|
||||||
checkContactsPermission()
|
checkContactsPermission()
|
||||||
|
checkLocationPermission()
|
||||||
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
|
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
|
||||||
appContext?.stopService(Intent(appContext, HttpService::class.java))
|
appContext?.stopService(Intent(appContext, HttpService::class.java))
|
||||||
} else {
|
} else {
|
||||||
@ -292,28 +306,21 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
|
|||||||
XToastUtils.error(String.format(getString(R.string.download_first), downloadPath))
|
XToastUtils.error(String.format(getString(R.string.download_first), downloadPath))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
MaterialDialog.Builder(requireContext())
|
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 ->
|
||||||
.title(getString(R.string.select_web_client_directory))
|
val webPath = "$downloadPath/$text"
|
||||||
.content(String.format(getString(R.string.root_directory), downloadPath))
|
binding!!.etWebPath.setText(webPath)
|
||||||
.items(dirList)
|
HttpServerUtils.serverWebPath = webPath
|
||||||
.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))
|
XToastUtils.info(getString(R.string.restarting_httpserver))
|
||||||
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
|
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
|
||||||
appContext?.stopService(Intent(appContext, HttpService::class.java))
|
appContext?.stopService(Intent(appContext, HttpService::class.java))
|
||||||
appContext?.startService(Intent(appContext, HttpService::class.java))
|
appContext?.startService(Intent(appContext, HttpService::class.java))
|
||||||
} else {
|
} else {
|
||||||
appContext?.startService(Intent(appContext, HttpService::class.java))
|
appContext?.startService(Intent(appContext, HttpService::class.java))
|
||||||
}
|
|
||||||
refreshButtonText()
|
|
||||||
true // allow selection
|
|
||||||
}
|
}
|
||||||
.positiveText(R.string.select)
|
refreshButtonText()
|
||||||
.negativeText(R.string.cancel)
|
true // allow selection
|
||||||
.show()
|
}.positiveText(R.string.select).negativeText(R.string.cancel).show()
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@ -342,8 +349,7 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
|
|||||||
private fun checkSendSmsPermission() {
|
private fun checkSendSmsPermission() {
|
||||||
XXPermissions.with(this)
|
XXPermissions.with(this)
|
||||||
// 发送短信
|
// 发送短信
|
||||||
.permission(Permission.SEND_SMS)
|
.permission(Permission.SEND_SMS).request(object : OnPermissionCallback {
|
||||||
.request(object : OnPermissionCallback {
|
|
||||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,8 +375,7 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
|
|||||||
// 发送短信
|
// 发送短信
|
||||||
.permission(Permission.SEND_SMS)
|
.permission(Permission.SEND_SMS)
|
||||||
// 读取短信
|
// 读取短信
|
||||||
.permission(Permission.READ_SMS)
|
.permission(Permission.READ_SMS).request(object : OnPermissionCallback {
|
||||||
.request(object : OnPermissionCallback {
|
|
||||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,8 +401,7 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
|
|||||||
// 读取手机号码
|
// 读取手机号码
|
||||||
.permission(Permission.READ_PHONE_NUMBERS)
|
.permission(Permission.READ_PHONE_NUMBERS)
|
||||||
// 读取通话记录
|
// 读取通话记录
|
||||||
.permission(Permission.READ_CALL_LOG)
|
.permission(Permission.READ_CALL_LOG).request(object : OnPermissionCallback {
|
||||||
.request(object : OnPermissionCallback {
|
|
||||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,24 +421,42 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
|
|||||||
|
|
||||||
//联系人权限
|
//联系人权限
|
||||||
private fun checkContactsPermission() {
|
private fun checkContactsPermission() {
|
||||||
XXPermissions.with(this)
|
XXPermissions.with(this).permission(*Permission.Group.CONTACTS).request(object : OnPermissionCallback {
|
||||||
.permission(*Permission.Group.CONTACTS)
|
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||||
.request(object : OnPermissionCallback {
|
}
|
||||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||||
if (never) {
|
if (never) {
|
||||||
XToastUtils.error(R.string.toast_denied_never)
|
XToastUtils.error(R.string.toast_denied_never)
|
||||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||||
} else {
|
} else {
|
||||||
XToastUtils.error(R.string.toast_denied)
|
XToastUtils.error(R.string.toast_denied)
|
||||||
}
|
|
||||||
HttpServerUtils.enableApiContactQuery = false
|
|
||||||
binding!!.sbApiQueryContacts.isChecked = false
|
|
||||||
}
|
}
|
||||||
})
|
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<String>, all: Boolean) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDenied(permissions: List<String>, 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() {
|
override fun onDestroy() {
|
||||||
|
@ -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<FragmentClientLocationBinding?>(), 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<String, Any> = 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<String, Any> = 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<String>() {
|
||||||
|
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<LocationInfo> = Gson().fromJson(json, object : TypeToken<BaseResponse<LocationInfo>>() {}.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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -37,6 +37,7 @@ class ConfigController {
|
|||||||
HttpServerUtils.enableApiContactQuery,
|
HttpServerUtils.enableApiContactQuery,
|
||||||
HttpServerUtils.enableApiBatteryQuery,
|
HttpServerUtils.enableApiBatteryQuery,
|
||||||
HttpServerUtils.enableApiWol,
|
HttpServerUtils.enableApiWol,
|
||||||
|
HttpServerUtils.enableApiLocation,
|
||||||
SettingUtils.extraDeviceMark,
|
SettingUtils.extraDeviceMark,
|
||||||
SettingUtils.extraSim1,
|
SettingUtils.extraSim1,
|
||||||
SettingUtils.extraSim2,
|
SettingUtils.extraSim2,
|
||||||
|
@ -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<EmptyData>): LocationInfo {
|
||||||
|
Log.d(TAG, bean.data.toString())
|
||||||
|
return HttpServerUtils.apiLocationCache
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,34 +1,36 @@
|
|||||||
package com.idormy.sms.forwarder.server.model
|
package com.idormy.sms.forwarder.server.model
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import com.idormy.sms.forwarder.entity.SimInfo
|
import com.idormy.sms.forwarder.entity.SimInfo
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
data class ConfigData(
|
data class ConfigData(
|
||||||
@SerializedName("enable_api_clone")
|
@SerializedName("enable_api_clone")
|
||||||
var enableApiClone: Boolean = false,
|
var enableApiClone: Boolean = false,
|
||||||
@SerializedName("enable_api_sms_send")
|
@SerializedName("enable_api_sms_send")
|
||||||
var enableApiSmsSend: Boolean = false,
|
var enableApiSmsSend: Boolean = false,
|
||||||
@SerializedName("enable_api_sms_query")
|
@SerializedName("enable_api_sms_query")
|
||||||
var enableApiSmsQuery: Boolean = false,
|
var enableApiSmsQuery: Boolean = false,
|
||||||
@SerializedName("enable_api_call_query")
|
@SerializedName("enable_api_call_query")
|
||||||
var enableApiCallQuery: Boolean = false,
|
var enableApiCallQuery: Boolean = false,
|
||||||
@SerializedName("enable_api_contact_query")
|
@SerializedName("enable_api_contact_query")
|
||||||
var enableApiContactQuery: Boolean = false,
|
var enableApiContactQuery: Boolean = false,
|
||||||
@SerializedName("enable_api_battery_query")
|
@SerializedName("enable_api_battery_query")
|
||||||
var enableApiBatteryQuery: Boolean = false,
|
var enableApiBatteryQuery: Boolean = false,
|
||||||
@SerializedName("enable_api_wol")
|
@SerializedName("enable_api_wol")
|
||||||
var enableApiWol: Boolean = false,
|
var enableApiWol: Boolean = false,
|
||||||
@SerializedName("extra_device_mark")
|
@SerializedName("enable_api_location")
|
||||||
var extraDeviceMark: String = "",
|
var enableApiLocation: Boolean = false,
|
||||||
@SerializedName("extra_sim1")
|
@SerializedName("extra_device_mark")
|
||||||
var extraSim1: String = "",
|
var extraDeviceMark: String = "",
|
||||||
@SerializedName("extra_sim2")
|
@SerializedName("extra_sim1")
|
||||||
var extraSim2: String = "",
|
var extraSim1: String = "",
|
||||||
@SerializedName("sim_info_list")
|
@SerializedName("extra_sim2")
|
||||||
var simInfoList: MutableMap<Int, SimInfo> = mutableMapOf(),
|
var extraSim2: String = "",
|
||||||
@SerializedName("version_code")
|
@SerializedName("sim_info_list")
|
||||||
var versionCode: Int = 0,
|
var simInfoList: MutableMap<Int, SimInfo> = mutableMapOf(),
|
||||||
@SerializedName("version_name")
|
@SerializedName("version_code")
|
||||||
var versionName: String = "",
|
var versionCode: Int = 0,
|
||||||
|
@SerializedName("version_name")
|
||||||
|
var versionName: String = "",
|
||||||
) : Serializable
|
) : Serializable
|
@ -1,66 +1,146 @@
|
|||||||
package com.idormy.sms.forwarder.service
|
package com.idormy.sms.forwarder.service
|
||||||
|
|
||||||
import android.app.Service
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.app.Service
|
||||||
import android.os.IBinder
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.location.Criteria
|
||||||
import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT
|
import android.location.Geocoder
|
||||||
import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT
|
import android.location.Location
|
||||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
import android.os.IBinder
|
||||||
import com.yanzhenjie.andserver.AndServer
|
import android.util.Log
|
||||||
import com.yanzhenjie.andserver.Server
|
import com.idormy.sms.forwarder.App
|
||||||
import java.util.concurrent.TimeUnit
|
import com.idormy.sms.forwarder.entity.LocationInfo
|
||||||
|
import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT
|
||||||
@Suppress("PrivatePropertyName")
|
import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT
|
||||||
class HttpService : Service(), Server.ServerListener {
|
import com.idormy.sms.forwarder.utils.HttpServerUtils
|
||||||
|
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||||
private val TAG: String = "HttpService"
|
import com.king.location.LocationClient
|
||||||
private val server by lazy {
|
import com.king.location.LocationErrorCode
|
||||||
AndServer.webServer(this)
|
import com.king.location.OnExceptionListener
|
||||||
.port(HTTP_SERVER_PORT)
|
import com.king.location.OnLocationListener
|
||||||
.listener(this)
|
import com.yanzhenjie.andserver.AndServer
|
||||||
.timeout(HTTP_SERVER_TIME_OUT, TimeUnit.SECONDS)
|
import com.yanzhenjie.andserver.Server
|
||||||
.build()
|
import java.text.SimpleDateFormat
|
||||||
}
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
override fun onBind(p0: Intent?): IBinder? {
|
|
||||||
return null
|
@SuppressLint("SimpleDateFormat")
|
||||||
}
|
@Suppress("PrivatePropertyName", "DEPRECATION")
|
||||||
|
class HttpService : Service(), Server.ServerListener {
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
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()
|
||||||
if (SettingUtils.enablePureClientMode) return
|
}
|
||||||
|
private val locationClient by lazy { LocationClient(App.context) }
|
||||||
Log.i(TAG, "onCreate: ")
|
private val geocoder by lazy { Geocoder(App.context) }
|
||||||
server.startup()
|
private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss") }
|
||||||
}
|
|
||||||
|
override fun onBind(p0: Intent?): IBinder? {
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
return null
|
||||||
Log.i(TAG, "onStartCommand: ")
|
}
|
||||||
return super.onStartCommand(intent, flags, startId)
|
|
||||||
}
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
//纯客户端模式
|
||||||
|
if (SettingUtils.enablePureClientMode) return
|
||||||
//纯客户端模式
|
|
||||||
if (SettingUtils.enablePureClientMode) return
|
Log.i(TAG, "onCreate: ")
|
||||||
|
server.startup()
|
||||||
Log.i(TAG, "onDestroy: ")
|
|
||||||
server.shutdown()
|
//远程找手机
|
||||||
}
|
if (HttpServerUtils.enableApiLocation) {
|
||||||
|
//可根据具体需求设置定位配置参数(这里只列出一些主要的参数)
|
||||||
override fun onException(e: Exception?) {
|
val locationOption = locationClient.getLocationOption().setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度
|
||||||
Log.i(TAG, "onException: ")
|
.setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗
|
||||||
}
|
.setMinTime(10000)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔:10000毫秒,最小间隔:1000毫秒
|
||||||
|
.setMinDistance(0)//设置位置更新最小距离(单位:米);默认距离:0米
|
||||||
override fun onStarted() {
|
.setOnceLocation(false)//设置是否只定位一次,默认为 false,当设置为 true 时,则只定位一次后,会自动停止定位
|
||||||
Log.i(TAG, "onStarted: ")
|
.setLastKnownLocation(true)//设置是否获取最后一次缓存的已知位置,默认为 true
|
||||||
}
|
//设置定位配置参数
|
||||||
|
locationClient.setLocationOption(locationOption)
|
||||||
override fun onStopped() {
|
locationClient.startLocation()
|
||||||
Log.i(TAG, "onStopped: ")
|
|
||||||
}
|
//设置定位监听
|
||||||
|
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: ")
|
||||||
|
}
|
||||||
}
|
}
|
@ -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_CONTACT_QUERY = "enable_api_contact_query"
|
||||||
const val SP_ENABLE_API_BATTERY_QUERY = "enable_api_battery_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_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_WOL_HISTORY = "wol_history"
|
||||||
const val SP_SERVER_ADDRESS = "server_address"
|
const val SP_SERVER_ADDRESS = "server_address"
|
||||||
const val SP_SERVER_HISTORY = "server_history"
|
const val SP_SERVER_HISTORY = "server_history"
|
||||||
@ -414,4 +416,11 @@ var CLIENT_FRAGMENT_LIST = listOf(
|
|||||||
CoreAnim.slide,
|
CoreAnim.slide,
|
||||||
R.drawable.icon_api_wol
|
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
|
||||||
|
),
|
||||||
)
|
)
|
@ -8,6 +8,7 @@ import com.google.gson.Gson
|
|||||||
import com.idormy.sms.forwarder.R
|
import com.idormy.sms.forwarder.R
|
||||||
import com.idormy.sms.forwarder.core.Core
|
import com.idormy.sms.forwarder.core.Core
|
||||||
import com.idormy.sms.forwarder.entity.CloneInfo
|
import com.idormy.sms.forwarder.entity.CloneInfo
|
||||||
|
import com.idormy.sms.forwarder.entity.LocationInfo
|
||||||
import com.idormy.sms.forwarder.server.model.BaseRequest
|
import com.idormy.sms.forwarder.server.model.BaseRequest
|
||||||
import com.xuexiang.xui.utils.ResUtils.getString
|
import com.xuexiang.xui.utils.ResUtils.getString
|
||||||
import com.xuexiang.xutil.app.AppUtils
|
import com.xuexiang.xutil.app.AppUtils
|
||||||
@ -84,6 +85,12 @@ class HttpServerUtils private constructor() {
|
|||||||
//是否启用远程WOL
|
//是否启用远程WOL
|
||||||
var enableApiWol: Boolean by SharedPreference(SP_ENABLE_API_WOL, true)
|
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历史记录
|
//WOL历史记录
|
||||||
var wolHistory: String by SharedPreference(SP_WOL_HISTORY, "")
|
var wolHistory: String by SharedPreference(SP_WOL_HISTORY, "")
|
||||||
|
|
||||||
|
BIN
app/src/main/res/drawable/icon_api_location.webp
Normal file
BIN
app/src/main/res/drawable/icon_api_location.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.0 KiB |
58
app/src/main/res/layout/fragment_client_location.xml
Normal file
58
app/src/main/res/layout/fragment_client_location.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/xui_config_color_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:overScrollMode="never">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:layout_width="250dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:contentDescription="@string/api_location"
|
||||||
|
app:srcCompat="@drawable/icon_api_location" />
|
||||||
|
|
||||||
|
<com.xuexiang.xui.widget.grouplist.XUIGroupListView
|
||||||
|
android:id="@+id/info_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
|
||||||
|
android:id="@+id/btn_refresh"
|
||||||
|
style="@style/SuperButton.Blue.Icon"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:drawableStart="@drawable/ic_refresh"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:text="@string/refresh"
|
||||||
|
tools:ignore="RtlSymmetry" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -732,6 +732,41 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
style="@style/settingBarStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/api_location"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:ignore="RelativeOverlap,TooManyViews" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/api_location_tips"
|
||||||
|
android:textSize="9sp"
|
||||||
|
tools:ignore="SmallSp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
|
||||||
|
android:id="@+id/sb_api_location"
|
||||||
|
style="@style/SwitchButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
@ -808,6 +808,13 @@
|
|||||||
<string name="api_battery_query_tips">Remotely query mobile phone power and battery status</string>
|
<string name="api_battery_query_tips">Remotely query mobile phone power and battery status</string>
|
||||||
<string name="api_wol">Remotely WOL</string>
|
<string name="api_wol">Remotely WOL</string>
|
||||||
<string name="api_wol_tips">Turn on your Wake-On-LAN enabled devices remotely</string>
|
<string name="api_wol_tips">Turn on your Wake-On-LAN enabled devices remotely</string>
|
||||||
|
<string name="api_location">Location</string>
|
||||||
|
<string name="api_location_tips">Remote query mobile phone location</string>
|
||||||
|
<string name="location_longitude">Longitude:%s</string>
|
||||||
|
<string name="location_latitude">Latitude:%s</string>
|
||||||
|
<string name="location_address">Address:%s</string>
|
||||||
|
<string name="location_time">Time:%s</string>
|
||||||
|
<string name="location_provider">Provider:%s</string>
|
||||||
|
|
||||||
<string name="sim_slot">Sim Slot</string>
|
<string name="sim_slot">Sim Slot</string>
|
||||||
<string name="phone_numbers">Phone Numbers</string>
|
<string name="phone_numbers">Phone Numbers</string>
|
||||||
|
@ -809,6 +809,13 @@
|
|||||||
<string name="api_battery_query_tips">远程查询手机电量与电池状态</string>
|
<string name="api_battery_query_tips">远程查询手机电量与电池状态</string>
|
||||||
<string name="api_wol">远程WOL</string>
|
<string name="api_wol">远程WOL</string>
|
||||||
<string name="api_wol_tips">远程打开启用LAN唤醒功能(Wake-On-LAN)的设备</string>
|
<string name="api_wol_tips">远程打开启用LAN唤醒功能(Wake-On-LAN)的设备</string>
|
||||||
|
<string name="api_location">远程找手机</string>
|
||||||
|
<string name="api_location_tips">远程查询手机定位,方便找回手机/防止老少走丢</string>
|
||||||
|
<string name="location_longitude">经度:%s</string>
|
||||||
|
<string name="location_latitude">维度:%s</string>
|
||||||
|
<string name="location_address">地址:%s</string>
|
||||||
|
<string name="location_time">时间:%s</string>
|
||||||
|
<string name="location_provider">供应商:%s</string>
|
||||||
|
|
||||||
<string name="sim_slot">发送卡槽</string>
|
<string name="sim_slot">发送卡槽</string>
|
||||||
<string name="phone_numbers">手机号码</string>
|
<string name="phone_numbers">手机号码</string>
|
||||||
|
@ -48,4 +48,3 @@ task clean(type: Delete) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user