一键连新增日志采集功能
This commit is contained in:
parent
d6896c3208
commit
fd6b5d9d2b
|
|
@ -28,6 +28,9 @@
|
|||
<!-- 适配Android 14以上 google 上架需要注意相关政策 -->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<!--通知权限-->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<application
|
||||
android:name=".app.MainApplication"
|
||||
android:fullBackupContent="@xml/full_backup_content"
|
||||
|
|
@ -59,7 +62,7 @@
|
|||
android:label="${APP_NAME}"
|
||||
android:process=":background"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/SplashTheme">
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
|
@ -143,6 +146,15 @@
|
|||
android:name=".act.NodeGroupActivity"
|
||||
android:process=":background"
|
||||
android:screenOrientation="portrait" />
|
||||
<service
|
||||
android:name=".levellog.LogcatService"
|
||||
android:exported="false"
|
||||
android:process=":background"
|
||||
android:label="@string/clash_logcat"
|
||||
android:foregroundServiceType="specialUse">
|
||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="explanation_for_special_use"/>
|
||||
</service>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -47,6 +50,7 @@ import com.yingmao.clash.entity.NodeGroupItem
|
|||
import com.yingmao.clash.fragment.home.HomeFragment
|
||||
import com.yingmao.clash.fragment.setting.SettingFragment
|
||||
import com.yingmao.clash.fragment.vip.vipFragment
|
||||
import com.yingmao.clash.levellog.AppLog
|
||||
import com.yingmao.clash.net.http.HttpApi
|
||||
import com.yingmao.clash.net.http.HttpReqType
|
||||
import com.yingmao.clash.net.http.HttpStatus
|
||||
|
|
@ -84,9 +88,6 @@ import java.util.UUID
|
|||
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
|
||||
|
||||
val fragments = arrayListOf<Fragment>()
|
||||
private lateinit var mainAtyVM: MainAtyVM
|
||||
private var payResultLoading: RxDialogShapeLoading? = null
|
||||
private val clashRunning: Boolean
|
||||
|
|
@ -161,6 +162,7 @@ class MainActivity : BaseActivity() {
|
|||
pm.update(it.uuid)
|
||||
newUuid = pro.uuid.toString()
|
||||
isSubbed = true
|
||||
AppLog.i("MainActivity", "刷新机场:${newUuid}")
|
||||
} ?: run {
|
||||
val uuid: UUID = pm.create(
|
||||
Profile.Type.Url,
|
||||
|
|
@ -216,6 +218,23 @@ class MainActivity : BaseActivity() {
|
|||
startLog()
|
||||
firstEntry()
|
||||
mainAtyVM.getExpensesRecord(mPageInfo)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val requestPermissionLauncher =
|
||||
registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted: Boolean ->
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
android.Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}
|
||||
//初始进入 将日志采集开关变为不开启
|
||||
MMKV.defaultMMKV().encode(BZYConstants.BehaviorLogSwitch, false)
|
||||
}
|
||||
|
||||
inner class NodeListBR : BroadcastReceiver() {
|
||||
|
|
@ -266,6 +285,7 @@ class MainActivity : BaseActivity() {
|
|||
aivHome.setImageResource(R.drawable.yjl_home_page_choose)
|
||||
aivCenter.setImageResource(R.drawable.yjl_user_page)
|
||||
aivVip.setImageResource(R.drawable.yjl_vip_page)
|
||||
checkVPN()
|
||||
}
|
||||
|
||||
aivCenter.setOnClickListener {
|
||||
|
|
@ -274,6 +294,7 @@ class MainActivity : BaseActivity() {
|
|||
aivHome.setImageResource(R.drawable.yjl_home_page)
|
||||
aivCenter.setImageResource(R.drawable.yjl_user_page_choose)
|
||||
aivVip.setImageResource(R.drawable.yjl_vip_page)
|
||||
checkVPN()
|
||||
}
|
||||
|
||||
aivVip.setOnClickListener {
|
||||
|
|
@ -334,6 +355,7 @@ class MainActivity : BaseActivity() {
|
|||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
subUrl?.let {
|
||||
Log.i("netSubUrl:${netSubUrl}::保存机场URL:=${proSubUrl}::subUrl:${subUrl}")
|
||||
AppLog.i("MainActivity", "网络获取机场netSubUrl:${netSubUrl}")
|
||||
setSub2(it)
|
||||
}
|
||||
}
|
||||
|
|
@ -376,7 +398,7 @@ class MainActivity : BaseActivity() {
|
|||
val oldExpireTime =
|
||||
mmkv.decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, 0)
|
||||
val nowExpireTime = checkLoginRsp.data!!.vpnExpireTime
|
||||
|
||||
AppLog.i("MainActivity", "刷新有效期:")
|
||||
if (nowExpireTime!=oldExpireTime) {
|
||||
val expired = checkLoginRsp.data.expired
|
||||
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true)
|
||||
|
|
@ -397,8 +419,6 @@ class MainActivity : BaseActivity() {
|
|||
}else if(checkLoginRsp.code == -2){
|
||||
RxToast.info(resources.getString(R.string.LoginOut))
|
||||
cleanUserInfo()
|
||||
}else {
|
||||
RxToast.info(resources.getString(R.string.QueryFailed))
|
||||
}
|
||||
payResultLoading?.dismiss()
|
||||
}
|
||||
|
|
@ -505,7 +525,7 @@ class MainActivity : BaseActivity() {
|
|||
|
||||
HttpReqType.CHECK_LOGIN -> {
|
||||
payResultLoading?.dismiss()
|
||||
RxToast.error(resources.getString(R.string.QueryFailed))
|
||||
RxToast.error(resources.getString(R.string.QueryFailed)+ httpStatus.msg)
|
||||
}
|
||||
|
||||
HttpReqType.GET_AIRNODES -> {
|
||||
|
|
@ -529,6 +549,7 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
fun cleanUserInfo() {
|
||||
AppLog.i("MainActivity", "cleanUserInfo===>退出登录")
|
||||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_PH_TOKEN)
|
||||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_TOKEN)
|
||||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_CURRENT_NODE)
|
||||
|
|
@ -783,4 +804,17 @@ class MainActivity : BaseActivity() {
|
|||
aivCenter.setImageResource(R.drawable.yjl_user_page)
|
||||
aivVip.setImageResource(R.drawable.yjl_vip_page_choose)
|
||||
}
|
||||
fun checkVPN(){
|
||||
val behaviorLogSwitch =
|
||||
MMKV.defaultMMKV().decodeBool(BZYConstants.BehaviorLogSwitch, false)
|
||||
if(behaviorLogSwitch){
|
||||
launch(Dispatchers.IO) {
|
||||
val isVPN =Utils.isVpnActive(this@MainActivity)
|
||||
AppLog.i("NewMainActivity", "BroadcastReceiver::isVPN::${isVPN}")
|
||||
val vpnCheChek =Utils.checkHttpOk("https://www.youtube.com")
|
||||
AppLog.i("NewMainActivity", "BroadcastReceiver::vpnCheChek::${vpnCheChek}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ import com.yingmao.clash.conf.BZYConstants
|
|||
import com.yingmao.clash.conf.Conf
|
||||
import com.yingmao.clash.entity.LanguageInfoEntity
|
||||
import com.yingmao.clash.fragment.home.HomeFragment.Companion.clashRunning
|
||||
import com.yingmao.clash.levellog.AppLog
|
||||
import com.yingmao.clash.net.http.HttpReqType
|
||||
import com.yingmao.clash.net.http.HttpStatus
|
||||
import com.yingmao.clash.net.http.entity.AppVersionCheckRsp
|
||||
|
|
@ -195,6 +196,7 @@ class SettingActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
private fun cleanUserInfo() {
|
||||
AppLog.i("SettingActivity", "退出登录 cleanUserInfo clashRunning==>${clashRunning}")
|
||||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_PH_TOKEN)
|
||||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_TOKEN)
|
||||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_CURRENT_NODE)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import android.widget.ProgressBar
|
|||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
|
|
@ -92,6 +94,12 @@ class SplashActivity : BaseActivity() {
|
|||
splashAtyVM = ViewModelProvider(this).get(SplashAtyVM::class.java)
|
||||
observe(this, splashAtyVM)
|
||||
setContentView(R.layout.layout_splash_activity)
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.rlMain)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
rlMain = findViewById(R.id.rlMain)
|
||||
rlPb = findViewById(R.id.rl_pb)
|
||||
llAction = findViewById(R.id.ll_action)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ import com.tencent.mmkv.MMKV
|
|||
import com.yingmao.clash.R
|
||||
import com.yingmao.clash.conf.Constants
|
||||
import com.yingmao.clash.entity.UserData
|
||||
import com.yingmao.clash.levellog.LogDispatcher
|
||||
import com.yingmao.clash.levellog.LogEvent
|
||||
import com.yingmao.clash.levellog.LogFileWriter
|
||||
import com.yingmao.clash.levellog.LogLevel
|
||||
import com.yingmao.clash.util.CrashCatchHandler
|
||||
import com.yingmao.clash.util.clashDir
|
||||
import com.yingmao.clash.view.dialog.DialogSureRestartChoose
|
||||
|
|
@ -138,6 +142,24 @@ class MainApplication : Application() {
|
|||
BaiduAction.init(this, Constants.USER_ACTION_SET_ID, Constants.APP_SECRET_KEY)
|
||||
// 设置应用激活的间隔,此方法在初始化成功后才有意义,建议在初始化后执行,具体执行的时机没有限制
|
||||
BaiduAction.setActivateInterval(this, 7)
|
||||
|
||||
LogFileWriter.init(this)
|
||||
|
||||
// 可选:捕获全局崩溃
|
||||
Thread.setDefaultUncaughtExceptionHandler { _, throwable ->
|
||||
LogDispatcher.enqueue(
|
||||
LogEvent(
|
||||
time = System.currentTimeMillis(),
|
||||
level = LogLevel.ERROR,
|
||||
tag = "Crash",
|
||||
message = "Uncaught exception",
|
||||
throwable = throwable
|
||||
)
|
||||
)
|
||||
|
||||
// 给日志落盘时间
|
||||
try { Thread.sleep(200) } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
private fun extractGeoFiles() {
|
||||
clashDir.mkdirs()
|
||||
|
|
|
|||
|
|
@ -18,14 +18,10 @@ import com.yingmao.clash.BuildConfig
|
|||
import com.yingmao.clash.R
|
||||
import com.yingmao.clash.conf.BZYConstants
|
||||
import com.yingmao.clash.conf.Conf
|
||||
//import com.yingmao.clash.design.model.DarkMode
|
||||
//import com.yingmao.clash.design.store.UiStore
|
||||
//import com.yingmao.clash.design.ui.DayNight
|
||||
import com.yingmao.clash.net.http.HttpStatus
|
||||
import com.yingmao.clash.remote.Broadcasts
|
||||
import com.yingmao.clash.remote.Remote
|
||||
import com.cncat.vpn.service.store.ServiceStore
|
||||
import com.yingmao.clash.conf.BZYConstants.Companion.ScreenFlag
|
||||
import com.yingmao.clash.util.StatusBarUtils
|
||||
import com.yingmao.clash.util.UiStore
|
||||
import com.yingmao.clash.view.dialog.RxDialogShapeLoading
|
||||
|
|
@ -54,16 +50,16 @@ open class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope(), Br
|
|||
window.decorView.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
|
||||
val decodeBool = MMKV.defaultMMKV().decodeBool(ScreenFlag, false)
|
||||
if(decodeBool){
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
|
||||
}else{
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE
|
||||
)
|
||||
}
|
||||
// val decodeBool = MMKV.defaultMMKV().decodeBool(ScreenFlag, false)
|
||||
// if(decodeBool){
|
||||
// window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
//
|
||||
// }else{
|
||||
// window.setFlags(
|
||||
// WindowManager.LayoutParams.FLAG_SECURE,
|
||||
// WindowManager.LayoutParams.FLAG_SECURE
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
override fun setContentView(layoutResID: Int) {
|
||||
|
|
|
|||
|
|
@ -70,5 +70,6 @@ class BZYConstants {
|
|||
const val applicationMode = "ApplicationMode"
|
||||
const val is_splash_first = "is_splash_first"
|
||||
const val ScreenFlag="screenshotFlag"
|
||||
const val BehaviorLogSwitch = "BehaviorLogSwitch"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.yingmao.clash.fragment.home
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity.RESULT_CANCELED
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
|
|
@ -19,6 +20,7 @@ import android.widget.RelativeLayout
|
|||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
@ -49,11 +51,11 @@ import com.yingmao.clash.act.MainActivity.Companion.profileBinder
|
|||
import com.yingmao.clash.act.MainActivity.Companion.subUrl
|
||||
import com.yingmao.clash.act.NodeGroupActivity
|
||||
import com.yingmao.clash.conf.BZYConstants
|
||||
import com.yingmao.clash.conf.BZYConstants.Companion.ScreenFlag
|
||||
import com.yingmao.clash.conf.Conf
|
||||
import com.yingmao.clash.entity.NodeGroupItem
|
||||
import com.yingmao.clash.fragment.AccelerateViewModel
|
||||
import com.yingmao.clash.fragment.BaseFragment
|
||||
import com.yingmao.clash.levellog.AppLog
|
||||
import com.yingmao.clash.net.http.HttpReqType
|
||||
import com.yingmao.clash.net.http.HttpStatus
|
||||
import com.yingmao.clash.net.http.entity.AppStartupStatus
|
||||
|
|
@ -61,6 +63,7 @@ import com.yingmao.clash.net.http.entity.ExpensesRecordRsp
|
|||
import com.yingmao.clash.net.http.entity.ShowNode
|
||||
import com.yingmao.clash.util.ActivityResultLifecycle
|
||||
import com.yingmao.clash.util.CommonUtils
|
||||
import com.yingmao.clash.util.Utils
|
||||
import com.yingmao.clash.util.startClashService
|
||||
import com.yingmao.clash.util.stopClashService
|
||||
import com.yingmao.clash.view.RxToast
|
||||
|
|
@ -127,6 +130,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
private var connectId = -1
|
||||
private var baseLoadingView: RxDialogShapeLoading? = null
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
override fun init(
|
||||
titleBarRl: RelativeLayout,
|
||||
backIv: ImageView,
|
||||
|
|
@ -200,6 +204,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
private fun initViews(view: View) {
|
||||
tvUserName = view.findViewById(R.id.tvUserName)
|
||||
tvUserTimeEnd= view.findViewById(R.id.tvUserTimeEnd)
|
||||
|
|
@ -219,10 +224,15 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
clLinks= view.findViewById(R.id.clLinks)
|
||||
|
||||
clNode.setOnClickListener {
|
||||
AppLog.i("HomeFragment", "choose node 选择节点")
|
||||
if (checkExpire()) {
|
||||
RxToast.info(resources.getString(R.string.AccountExpired))
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (clashRunning) {
|
||||
RxToast.info(resources.getString(R.string.CloseChooseNode))
|
||||
return@setOnClickListener
|
||||
}
|
||||
if(clashRunning){
|
||||
val getIsGlo= MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false)
|
||||
if(getIsGlo){
|
||||
|
|
@ -239,22 +249,32 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
startActivityForResult(intent, 2)
|
||||
}
|
||||
tvIntelligentMode.setOnClickListener {
|
||||
if (!clashRunning) {
|
||||
RxToast.info(resources.getString(R.string.OpenSwitchingSourceModel))
|
||||
return@setOnClickListener
|
||||
}
|
||||
val getIsGlo= MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false)
|
||||
Log.d("clIntelligentMode ==>${getIsGlo}")
|
||||
if(getIsGlo){
|
||||
showLoading(resources.getString(R.string.modelchanging), requireActivity())
|
||||
setProxyModeView(ProxyMode.PAC)
|
||||
MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false)
|
||||
checkGlo()
|
||||
checkGlo(true)
|
||||
}
|
||||
|
||||
}
|
||||
tvGlobalMode.setOnClickListener {
|
||||
if (!clashRunning) {
|
||||
RxToast.info(resources.getString(R.string.OpenSwitchingSourceModel))
|
||||
return@setOnClickListener
|
||||
}
|
||||
val getIsGlo= MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false)
|
||||
Log.d("clGlobalModel ==>${getIsGlo}")
|
||||
if (!getIsGlo) {
|
||||
showLoading(resources.getString(R.string.modelchanging), requireActivity())
|
||||
setProxyModeView(ProxyMode.GLOBAL)
|
||||
MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_GLOBAL_MODEL, true)
|
||||
checkGlo()
|
||||
checkGlo(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -309,8 +329,15 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
}
|
||||
return@setOnClickListener
|
||||
}
|
||||
val networkAvailable = Utils.isNetworkAvailable(requireActivity())
|
||||
if(networkAvailable){
|
||||
showLoading(resources.getString(R.string.Starting), requireContext())
|
||||
startClash(true)
|
||||
//开启完毕之后开启定时器,
|
||||
}else{
|
||||
RxToast.error("网络不可用,请打开网络!")
|
||||
AppLog.i("NewAccFragment", "networkAvailable ${networkAvailable}")
|
||||
}
|
||||
}
|
||||
}
|
||||
aivCustomService.setOnClickListener {
|
||||
|
|
@ -361,8 +388,11 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
}
|
||||
|
||||
private fun startClash(isStart: Boolean) {
|
||||
AppLog.i("NewAccFragment", "onClick startClash()")
|
||||
val nodeJson=serStore.nodes
|
||||
Log.d("nodeJson: ${nodeJson.isEmpty()}")
|
||||
AppLog.i("NewAccFragment", "subUrl:::${subUrl}")
|
||||
AppLog.i("NewAccFragment", "nodeJson:::${nodeJson}")
|
||||
if(nodeJson.isEmpty()){
|
||||
RxToast.info(resources.getString(R.string.GetNodesAgined))
|
||||
refreshNode()
|
||||
|
|
@ -402,6 +432,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
try {
|
||||
startUTimer()
|
||||
if (vpnRequest != null) {
|
||||
AppLog.i("NewAccFragment", "vpnRequest::VPN权限未获取 vpnRequest != null")
|
||||
val result = startActivityForResult(
|
||||
ActivityResultContracts.StartActivityForResult(), vpnRequest
|
||||
)
|
||||
|
|
@ -410,6 +441,14 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
stateChanged(CLASH_STARTING, true)
|
||||
context?.startClashService()
|
||||
accelerateViewModel.appStartupStatus()
|
||||
AppLog.i("NewAccFragment", "vpnRequest::VPN权限点击同意")
|
||||
}else if(result.resultCode == RESULT_CANCELED){
|
||||
withContext(Dispatchers.Main){
|
||||
hideLoading()
|
||||
stateChanged(CLASH_STOP, false)
|
||||
RxToast.info(resources.getString(R.string.vpnStartingError))
|
||||
AppLog.i("NewAccFragment", "vpnRequest::VPN权限点击取消")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
accelerateViewModel.appStartupStatus()
|
||||
|
|
@ -461,7 +500,10 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
if (isAdded) {
|
||||
RxToast.success(resources.getString(R.string.AccelerateSuccess))
|
||||
}
|
||||
checkGlo()
|
||||
checkGlo(false)
|
||||
ivAccelerate.setImageResource(R.mipmap.yjl_home_clink_select_bg)
|
||||
AppLog.i("HomeFragment", "收到打开VPN广播 clashRunning==>${clashRunning}")
|
||||
|
||||
}
|
||||
|
||||
Intents.ACTION_CLASH_STOPPED -> {
|
||||
|
|
@ -469,7 +511,9 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
rxDialogShapeLoading.cancel()
|
||||
}
|
||||
clashRunning = false
|
||||
ivAccelerate.setImageResource(R.mipmap.yjl_home_clink_bg)
|
||||
stopUTimer()
|
||||
AppLog.i("HomeFragment", "收到关闭VPN广播 clashRunning==>${clashRunning}")
|
||||
}
|
||||
|
||||
Intents.ACTION_CLASH_REQUEST_STOP -> {
|
||||
|
|
@ -488,6 +532,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
val mode = intent.getBooleanExtra("mode", false)
|
||||
Log.d("ACTION_Glo_Mode mode==>${mode}")
|
||||
isGlo = mode
|
||||
AppLog.i("HomeFragment", "收到模式切换广播 isGlo==>${isGlo}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -549,7 +594,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
} else {
|
||||
startClash(false)
|
||||
}
|
||||
checkGlo()
|
||||
checkGlo(false)
|
||||
}?.run {
|
||||
if (isRunning == true && !clashRunning) {
|
||||
startClash(false)
|
||||
|
|
@ -659,13 +704,13 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
}
|
||||
|
||||
|
||||
private fun checkGlo() {
|
||||
private fun checkGlo(isUIChange: Boolean) {
|
||||
val isGlobal =isGlo
|
||||
Log.i("checkGlo isGlobal==>${isGlobal} clashRunning==>${clashRunning}")
|
||||
launch(Dispatchers.IO) {
|
||||
if (isGlobal) {
|
||||
if (clashRunning) {
|
||||
getNodes()
|
||||
getNodes(isUIChange)
|
||||
}
|
||||
}else{
|
||||
if(clashRunning){
|
||||
|
|
@ -676,11 +721,9 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
if (names.isNotEmpty()) {
|
||||
var selectedName =
|
||||
MMKV.defaultMMKV()
|
||||
.decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "")
|
||||
if(TextUtils.isEmpty(selectedName)){
|
||||
selectedName="自动选择"
|
||||
}
|
||||
.decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "自动选择")
|
||||
Log.i("selected_name ==>${selectedName}")
|
||||
AppLog.i("NewAccFragment", "selected_name::${selectedName}")
|
||||
if (!TextUtils.isEmpty(selectedName)) {
|
||||
val o = cm.queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Rule
|
||||
|
|
@ -693,6 +736,12 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
}
|
||||
}
|
||||
cm.patchSelector(gName, selectedName!!)
|
||||
if(isUIChange){
|
||||
withContext(Dispatchers.Main){
|
||||
hideLoading()
|
||||
RxToast.info(resources.getString(R.string.modelchangSuccess))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -701,7 +750,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getNodes() {
|
||||
private suspend fun getNodes(isUIChange: Boolean) {
|
||||
coroutineScope {
|
||||
val selectedName =
|
||||
MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "")
|
||||
|
|
@ -740,6 +789,12 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
}
|
||||
|
||||
cm.patchSelector("GLOBAL", baseNodeName)
|
||||
if(isUIChange){
|
||||
withContext(Dispatchers.Main){
|
||||
hideLoading()
|
||||
RxToast.info(resources.getString(R.string.modelchangSuccess))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -769,6 +824,12 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
}
|
||||
|
||||
cm.patchSelector("GLOBAL", selectedName!!)
|
||||
if(isUIChange){
|
||||
withContext(Dispatchers.Main){
|
||||
hideLoading()
|
||||
RxToast.info(resources.getString(R.string.modelchangSuccess))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -820,24 +881,24 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
|
|||
clProxy.visibility=View.VISIBLE
|
||||
clNode.visibility=View.VISIBLE
|
||||
}
|
||||
val screenshotFlag = it.data?.screenshotFlag
|
||||
screenshotFlag.let {
|
||||
if (screenshotFlag == true) {
|
||||
// 在 Activity 中取消禁止截屏/录屏
|
||||
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
MMKV.defaultMMKV().encode(
|
||||
ScreenFlag, screenshotFlag
|
||||
)
|
||||
} else {
|
||||
requireActivity().window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE
|
||||
)
|
||||
MMKV.defaultMMKV().encode(
|
||||
ScreenFlag, screenshotFlag!!
|
||||
)
|
||||
}
|
||||
}
|
||||
// val screenshotFlag = it.data?.screenshotFlag
|
||||
// screenshotFlag.let {
|
||||
// if (screenshotFlag == true) {
|
||||
// // 在 Activity 中取消禁止截屏/录屏
|
||||
// requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
// MMKV.defaultMMKV().encode(
|
||||
// ScreenFlag, screenshotFlag
|
||||
// )
|
||||
// } else {
|
||||
// requireActivity().window.setFlags(
|
||||
// WindowManager.LayoutParams.FLAG_SECURE,
|
||||
// WindowManager.LayoutParams.FLAG_SECURE
|
||||
// )
|
||||
// MMKV.defaultMMKV().encode(
|
||||
// ScreenFlag, screenshotFlag!!
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.yingmao.clash.fragment.setting
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application.getProcessName
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
|
|
@ -21,6 +22,8 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.alibaba.sdk.android.oss.ClientException
|
||||
import com.alibaba.sdk.android.oss.OSSClient
|
||||
import com.alibaba.sdk.android.oss.ServiceException
|
||||
|
|
@ -30,7 +33,9 @@ import com.alibaba.sdk.android.oss.common.auth.OSSStsTokenCredentialProvider
|
|||
import com.alibaba.sdk.android.oss.internal.OSSAsyncTask
|
||||
import com.alibaba.sdk.android.oss.model.PutObjectRequest
|
||||
import com.alibaba.sdk.android.oss.model.PutObjectResult
|
||||
import com.cncat.vpn.common.compat.startForegroundServiceCompat
|
||||
import com.cncat.vpn.common.log.Log
|
||||
import com.cncat.vpn.common.util.intent
|
||||
import com.hjq.permissions.OnPermissionCallback
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
|
|
@ -42,6 +47,7 @@ import com.yingmao.clash.act.ActivityActivity
|
|||
import com.yingmao.clash.act.AgreementAndPrivacyActivity
|
||||
import com.yingmao.clash.act.BandPhoneActivity
|
||||
import com.yingmao.clash.act.MainActivity
|
||||
import com.yingmao.clash.act.MainActivity.Companion.newUuid
|
||||
import com.yingmao.clash.act.MainActivity.Companion.profileBinder
|
||||
import com.yingmao.clash.act.MainActivity.Companion.setSub2
|
||||
import com.yingmao.clash.act.MainActivity.Companion.subUrl
|
||||
|
|
@ -50,10 +56,12 @@ import com.yingmao.clash.act.SettingActivity
|
|||
import com.yingmao.clash.act.ShareActivity
|
||||
import com.yingmao.clash.act.SplashHelper
|
||||
import com.yingmao.clash.app.MainApplication
|
||||
import com.yingmao.clash.levellog.LogcatService
|
||||
import com.yingmao.clash.conf.BZYConstants
|
||||
import com.yingmao.clash.entity.SettingPageBean
|
||||
import com.yingmao.clash.fragment.BaseFragment
|
||||
import com.yingmao.clash.fragment.home.HomeFragment.Companion.clashRunning
|
||||
import com.yingmao.clash.levellog.AppLog
|
||||
import com.yingmao.clash.net.http.HttpReqType
|
||||
import com.yingmao.clash.net.http.HttpStatus
|
||||
import com.yingmao.clash.net.http.entity.ParamData
|
||||
|
|
@ -108,7 +116,7 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
|
|||
private var baseLoad: RxDialogShapeLoading? = null
|
||||
private lateinit var mainAtyVM: MainAtyVM
|
||||
private var settingPageList = mutableListOf<SettingPageBean>()
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
override fun init(
|
||||
titleBarRl: RelativeLayout,
|
||||
backIv: ImageView,
|
||||
|
|
@ -125,7 +133,7 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
|
|||
container.addView(view)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun initData() {
|
||||
val userName =
|
||||
|
|
@ -176,12 +184,24 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
|
|||
R.drawable.switching_item_tag,
|
||||
)
|
||||
|
||||
|
||||
val upLoadData= SettingPageBean(
|
||||
val logDirParent = "applogs"
|
||||
val logDir = File(requireActivity().filesDir, logDirParent)
|
||||
val processName =getProcessName()
|
||||
val logFile = File(logDir, "$processName.log")
|
||||
val upLoadData = if(logFile.exists()){
|
||||
SettingPageBean(
|
||||
7,
|
||||
resources.getString(R.string.UploadLog),
|
||||
"上传日志",
|
||||
R.drawable.user_uplog_icon,
|
||||
)
|
||||
}else{
|
||||
SettingPageBean(
|
||||
7,
|
||||
"采集日志",
|
||||
R.drawable.user_uplog_icon,
|
||||
)
|
||||
}
|
||||
|
||||
val privacyPolicyItem= SettingPageBean(
|
||||
8,
|
||||
resources.getString(R.string.PrivacyPolicy),
|
||||
|
|
@ -221,6 +241,7 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
|
|||
if(itemData!=null){
|
||||
when(itemData.type){
|
||||
1->{
|
||||
AppLog.i("SettingFragment", "SettingFragment:点击设置")
|
||||
startActivity(Intent(requireContext(), SettingActivity::class.java))
|
||||
}
|
||||
2->{
|
||||
|
|
@ -273,35 +294,31 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
|
|||
|
||||
}
|
||||
7->{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
showLoading(
|
||||
resources.getString(R.string.UploadingInProgress),
|
||||
requireActivity()
|
||||
)
|
||||
// 已授予权限
|
||||
val directory: File? = requireActivity().getExternalFilesDir("CrashLog")
|
||||
if (directory != null) {
|
||||
val allFilePath = getAllFilePath(directory.path)
|
||||
if (allFilePath.isNotEmpty()) {
|
||||
if(itemData.name=="上传日志"){
|
||||
showLoading(resources.getString(R.string.UploadingInProgress), requireActivity())
|
||||
mainAtyVM.getUploadParameters()
|
||||
} else {
|
||||
android.util.Log.i("checkPermission", "allFilePath is empty")
|
||||
hideLoading()
|
||||
RxToast.info(resources.getString(R.string.NoErrorLogs))
|
||||
settingPageList[itemData.type-1].name="采集日志"
|
||||
settingAdapter.notifyItemChanged(itemData.type-1)
|
||||
MMKV.defaultMMKV().encode(BZYConstants.BehaviorLogSwitch, false)
|
||||
requireActivity().stopService(LogcatService::class.intent)
|
||||
}else{
|
||||
MaterialDialog(requireActivity()).show {
|
||||
this.cancelable(true)
|
||||
cornerRadius(res = R.dimen.dp_15)
|
||||
customView(
|
||||
R.layout.logsings_dialog_layout,
|
||||
scrollable = false,
|
||||
noVerticalPadding = true,
|
||||
horizontalPadding = false
|
||||
)
|
||||
val tvSure = findViewById<TextView>(R.id.tvSure)
|
||||
tvSure.setOnClickListener {
|
||||
settingPageList[itemData.type-1].name="上传日志"
|
||||
settingAdapter.notifyItemChanged(itemData.type-1)
|
||||
requireActivity().startForegroundServiceCompat(LogcatService::class.intent)
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
hideLoading()
|
||||
RxToast.error(resources.getString(R.string.NoErrorLogs))
|
||||
}
|
||||
} else {
|
||||
// 未授予权限,跳转到设置页面
|
||||
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
startActivity(intent)
|
||||
hideLoading()
|
||||
}
|
||||
} else {
|
||||
checkPermission()
|
||||
}
|
||||
}
|
||||
8->{
|
||||
|
|
@ -572,6 +589,7 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
|
|||
super.httpLoading(httpStatus)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
override fun httpSuccess(httpStatus: HttpStatus.Success) {
|
||||
when (httpStatus.reqType) {
|
||||
|
||||
|
|
@ -621,26 +639,24 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
|
|||
HttpReqType.UploadParameters -> {
|
||||
val upLoadData = httpStatus.rsp as UploadParamData
|
||||
if (upLoadData.code == 1000) {
|
||||
val directory: File? = requireActivity().getExternalFilesDir("CrashLog")
|
||||
//多文件上传
|
||||
if (directory != null) {
|
||||
val allFilePath = getAllFilePath(directory.path)
|
||||
if (upLoadData.data != null) {
|
||||
if (allFilePath.isNotEmpty()) {
|
||||
val logDirParent = "applogs"
|
||||
val logDir = File(requireActivity().filesDir, logDirParent)
|
||||
val processName = getProcessName()
|
||||
val logFile = File(logDir, "$processName.log")
|
||||
val username =
|
||||
MMKV.defaultMMKV()
|
||||
.decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
|
||||
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
|
||||
val nowTime = SimpleDateFormat(
|
||||
"yyyyMMddkkmmss",
|
||||
Locale.getDefault()
|
||||
).format(Date())
|
||||
uploadForAliOss(
|
||||
upLoadData.data,
|
||||
allFilePath,
|
||||
"app/errlog/" + username + "_" + nowTime + ".txt"
|
||||
if(logFile.exists()){
|
||||
upBehaviorLog(
|
||||
upLoadData.data!!,
|
||||
logFile.path,
|
||||
"app/errlog/"+"${BuildConfig.applicationId}/"+"${username}/" + username + "_" + nowTime + ".txt"
|
||||
)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
hideLoading()
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
@ -831,4 +847,65 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
|
|||
}
|
||||
})
|
||||
}
|
||||
private fun upBehaviorLog(data: ParamData, filePath: String, dirStr: String) {
|
||||
val endpoint = "oss-cn-hongkong.aliyuncs.com"
|
||||
val accessKeyId = data.credentials?.accessKeyId
|
||||
val accessKeySecret = data.credentials?.accessKeySecret
|
||||
// 从STS服务获取的安全令牌(SecurityToken)。
|
||||
val securityToken = data.credentials?.securityToken
|
||||
val credentialProvider =
|
||||
OSSStsTokenCredentialProvider(accessKeyId, accessKeySecret, securityToken)
|
||||
val oss = OSSClient(requireActivity(), endpoint, credentialProvider)
|
||||
android.util.Log.i("uploadForAliOss", "filePath::::${filePath} dirStr:::${dirStr} ")
|
||||
val put = PutObjectRequest("yingmao", dirStr, filePath)
|
||||
// 异步上传时可以设置进度回调。
|
||||
put.progressCallback =
|
||||
OSSProgressCallback { request, currentSize, totalSize ->
|
||||
Log.i("progressCallback==> currentSize: $currentSize totalSize: $totalSize")
|
||||
}
|
||||
|
||||
oss.asyncPutObject(put,
|
||||
object : OSSCompletedCallback<PutObjectRequest?, PutObjectResult> {
|
||||
override fun onSuccess(request: PutObjectRequest?, result: PutObjectResult) {
|
||||
Log.i("PutObjectRequest result.statusCode: ${result.statusCode}")
|
||||
if (result.statusCode==200) {
|
||||
requireActivity().runOnUiThread {
|
||||
hideLoading()
|
||||
val deleteFile = DataUtils.deleteFile(filePath)
|
||||
Log.i("PutObjectRequest deleteFile ${deleteFile}")
|
||||
AppLog.i("LogcatService", "删除删除成功deleteFile::${deleteFile}")
|
||||
MMKV.defaultMMKV().encode(BZYConstants.BehaviorLogSwitch, false)
|
||||
RxToast.info(resources.getString(R.string.BehaviorLogSuccessfully))
|
||||
}
|
||||
}else{
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(
|
||||
request: PutObjectRequest?,
|
||||
clientExcepion: ClientException,
|
||||
serviceException: ServiceException
|
||||
) {
|
||||
requireActivity().runOnUiThread {
|
||||
hideLoading()
|
||||
RxToast.error(resources.getString(R.string.BehaviorLogError))
|
||||
}
|
||||
// 请求异常。
|
||||
if (clientExcepion != null) {
|
||||
// 本地异常,如网络异常等。
|
||||
clientExcepion.printStackTrace()
|
||||
}
|
||||
if (serviceException != null) {
|
||||
// 服务异常。
|
||||
android.util.Log.e("ErrorCode", serviceException.errorCode)
|
||||
android.util.Log.e("RequestId", serviceException.requestId)
|
||||
android.util.Log.e("HostId", serviceException.hostId)
|
||||
android.util.Log.e("RawMessage", serviceException.rawMessage)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.yingmao.clash.levellog
|
||||
|
||||
import com.cncat.vpn.common.log.Log
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.yingmao.clash.conf.BZYConstants
|
||||
|
||||
object AppLog {
|
||||
|
||||
fun v(tag: String, msg: String) =
|
||||
log(LogLevel.VERBOSE, tag, msg, null)
|
||||
|
||||
fun d(tag: String, msg: String) =
|
||||
log(LogLevel.DEBUG, tag, msg, null)
|
||||
|
||||
fun i(tag: String, msg: String) =
|
||||
log(LogLevel.INFO, tag, msg, null)
|
||||
|
||||
fun w(tag: String, msg: String, tr: Throwable? = null) =
|
||||
log(LogLevel.WARN, tag, msg, tr)
|
||||
|
||||
fun e(tag: String, msg: String, tr: Throwable? = null) =
|
||||
log(LogLevel.ERROR, tag, msg, tr)
|
||||
|
||||
private fun log(
|
||||
level: LogLevel,
|
||||
tag: String,
|
||||
msg: String,
|
||||
tr: Throwable?
|
||||
) {
|
||||
val behaviorLogSwitch =
|
||||
MMKV.defaultMMKV().decodeBool(BZYConstants.BehaviorLogSwitch, false)
|
||||
Log.i("AAAAAAAAAAAA behaviorLogSwitch ${behaviorLogSwitch}")
|
||||
if(behaviorLogSwitch){
|
||||
LogDispatcher.enqueue(
|
||||
LogEvent(
|
||||
time = System.currentTimeMillis(),
|
||||
level = level,
|
||||
tag = tag,
|
||||
message = msg,
|
||||
throwable = tr
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.yingmao.clash.levellog
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
object LogDispatcher {
|
||||
|
||||
private val scope = CoroutineScope(
|
||||
SupervisorJob() + Dispatchers.IO
|
||||
)
|
||||
|
||||
fun enqueue(event: LogEvent) {
|
||||
scope.launch {
|
||||
LogFileWriter.write(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.yingmao.clash.levellog
|
||||
|
||||
data class LogEvent(
|
||||
val time: Long,
|
||||
val level: LogLevel,
|
||||
val tag: String,
|
||||
val message: String,
|
||||
val throwable: Throwable? = null
|
||||
)
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package com.yingmao.clash.levellog
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
object LogFileWriter {
|
||||
|
||||
private const val LOG_DIR = "applogs"
|
||||
private const val MAX_FILE_SIZE = 1 * 1024 * 1024 // 1MB
|
||||
private const val MAX_BACKUP_COUNT = 3
|
||||
|
||||
private lateinit var logDir: File
|
||||
private lateinit var logFile: File
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
private val timeFormat =
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
|
||||
|
||||
/**
|
||||
* 必须在 Application.onCreate 调用
|
||||
*/
|
||||
fun init(context: Context) {
|
||||
val processName = getProcessName()
|
||||
logDir = File(context.filesDir, LOG_DIR)
|
||||
if (!logDir.exists()) {
|
||||
logDir.mkdirs()
|
||||
}
|
||||
|
||||
logFile = File(logDir, "$processName.log")
|
||||
}
|
||||
|
||||
fun write(event: LogEvent) {
|
||||
if (!::logFile.isInitialized) return
|
||||
|
||||
lock.withLock {
|
||||
rotateIfNeeded()
|
||||
|
||||
FileWriter(logFile, true).use { fw ->
|
||||
PrintWriter(fw).use { pw ->
|
||||
pw.println(format(event))
|
||||
pw.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================
|
||||
// 内部实现
|
||||
// =====================
|
||||
|
||||
private fun rotateIfNeeded() {
|
||||
if (!logFile.exists()) return
|
||||
if (logFile.length() < MAX_FILE_SIZE) return
|
||||
|
||||
// 删除最老的
|
||||
val oldest = File(logDir, "${logFile.name}.$MAX_BACKUP_COUNT")
|
||||
if (oldest.exists()) {
|
||||
oldest.delete()
|
||||
}
|
||||
|
||||
// 依次后移
|
||||
for (i in MAX_BACKUP_COUNT - 1 downTo 1) {
|
||||
val src = File(logDir, "${logFile.name}.$i")
|
||||
if (src.exists()) {
|
||||
src.renameTo(File(logDir, "${logFile.name}.${i + 1}"))
|
||||
}
|
||||
}
|
||||
|
||||
// 当前 -> .1
|
||||
logFile.renameTo(File(logDir, "${logFile.name}.1"))
|
||||
|
||||
// 新建空文件
|
||||
logFile = File(logDir, logFile.name)
|
||||
}
|
||||
|
||||
private fun format(event: LogEvent): String {
|
||||
val time = timeFormat.format(Date(event.time))
|
||||
val pid = Process.myPid()
|
||||
val tid = Thread.currentThread().name
|
||||
|
||||
val sb = StringBuilder(256)
|
||||
sb.append("[")
|
||||
.append(time)
|
||||
.append("]")
|
||||
.append("[")
|
||||
.append(event.level.short)
|
||||
.append("]")
|
||||
.append("[pid=").append(pid)
|
||||
.append(",tid=").append(tid)
|
||||
.append("]")
|
||||
.append("[")
|
||||
.append(event.tag)
|
||||
.append("] ")
|
||||
.append(event.message)
|
||||
|
||||
event.throwable?.let {
|
||||
sb.append("\n")
|
||||
sb.append(Log.getStackTraceString(it))
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun getProcessName(): String {
|
||||
return if (Build.VERSION.SDK_INT >= 28) {
|
||||
Application.getProcessName()
|
||||
} else {
|
||||
readProcName()
|
||||
}
|
||||
}
|
||||
|
||||
private fun readProcName(): String {
|
||||
return try {
|
||||
File("/proc/self/cmdline")
|
||||
.readText()
|
||||
.trim()
|
||||
.replace('\u0000', '_')
|
||||
} catch (e: Exception) {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.yingmao.clash.levellog
|
||||
|
||||
enum class LogLevel(val short: String) {
|
||||
VERBOSE("V"),
|
||||
DEBUG("D"),
|
||||
INFO("I"),
|
||||
WARN("W"),
|
||||
ERROR("E")
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
package com.yingmao.clash.levellog
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.IInterface
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.cncat.vpn.common.compat.getColorCompat
|
||||
import com.cncat.vpn.common.compat.pendingIntentFlags
|
||||
import com.cncat.vpn.common.compat.startForegroundCompat
|
||||
import com.cncat.vpn.common.util.intent
|
||||
import com.cncat.vpn.core.model.LogMessage
|
||||
import com.cncat.vpn.service.RemoteService
|
||||
import com.cncat.vpn.service.remote.ILogObserver
|
||||
import com.cncat.vpn.service.remote.IRemoteService
|
||||
import com.cncat.vpn.service.remote.unwrap
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.yingmao.clash.R
|
||||
import com.yingmao.clash.act.SettingActivity
|
||||
import com.yingmao.clash.app.MainApplication
|
||||
import com.yingmao.clash.conf.BZYConstants
|
||||
import com.yingmao.clash.util.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
|
||||
class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.Default), IInterface {
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
Log.i("LogcatServiceClose","onServiceDisconnected::${name}")
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.KITKAT)
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
Log.i("LogcatServiceConnect","onServiceConnected::${name}")
|
||||
startObserver(service ?: return stopSelf())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
running = true
|
||||
|
||||
createNotificationChannel()
|
||||
|
||||
showNotification()
|
||||
|
||||
bindService(RemoteService::class.intent, connection, BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
cancel()
|
||||
|
||||
unbindService(connection)
|
||||
|
||||
stopForeground(true)
|
||||
|
||||
running = false
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return this.asBinder()
|
||||
}
|
||||
|
||||
override fun asBinder(): IBinder {
|
||||
return object : Binder() {
|
||||
override fun queryLocalInterface(descriptor: String): IInterface {
|
||||
return this@LogcatService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.KITKAT)
|
||||
private fun startObserver(binder: IBinder) {
|
||||
if (!binder.isBinderAlive)
|
||||
return stopSelf()
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
val service = binder.unwrap(IRemoteService::class).clash()
|
||||
val channel = Channel<LogMessage>(CACHE_CAPACITY)
|
||||
|
||||
try {
|
||||
MMKV.defaultMMKV().encode(BZYConstants.Companion.BehaviorLogSwitch, true)
|
||||
val selectedName =
|
||||
MMKV.defaultMMKV().decodeString(BZYConstants.Companion.NODE_SELECTED_NAME_KEY, "")
|
||||
val equipmentModel = Utils.getEquipmentModel()
|
||||
AppLog.i("LogcatService", "selectedName===>${selectedName}")
|
||||
AppLog.i("LogcatService", "用户是否过期===>${checkExpire()}")
|
||||
AppLog.i("LogcatService", "机型数据===>${equipmentModel}")
|
||||
AppLog.i("LogcatService", "OAID===>${MainApplication.Companion.OAID}")
|
||||
val observer = object : ILogObserver {
|
||||
override fun newItem(log: LogMessage) {
|
||||
channel.trySend(log)
|
||||
}
|
||||
}
|
||||
service.setLogObserver(observer)
|
||||
while (isActive) {
|
||||
val msg = channel.receive()
|
||||
AppLog.i("LogcatServiceMsg", "core msg===>${msg}")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("Write log file: $e", e.toString())
|
||||
} finally {
|
||||
withContext(NonCancellable) {
|
||||
if (binder.isBinderAlive) {
|
||||
service.setLogObserver(null)
|
||||
}
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
NotificationManagerCompat.from(this)
|
||||
.createNotificationChannel(
|
||||
NotificationChannelCompat.Builder(
|
||||
CHANNEL_ID,
|
||||
NotificationManagerCompat.IMPORTANCE_DEFAULT
|
||||
).setName(getString(R.string.clash_logcat)).build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun showNotification() {
|
||||
val notification = NotificationCompat
|
||||
.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.mipmap.oneclickconnection)
|
||||
.setColor(getColorCompat(R.color.color_clash))
|
||||
.setContentTitle(getString(R.string.clash_logcat))
|
||||
.setContentText("采集中")
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
R.id.nf_logcat_status,
|
||||
SettingActivity::class.intent
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP),
|
||||
pendingIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
)
|
||||
.build()
|
||||
|
||||
startForegroundCompat(R.id.nf_logcat_status, notification)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ID = "clash_logcat_channel_id"
|
||||
private const val CACHE_CAPACITY = 129
|
||||
|
||||
var running: Boolean = false
|
||||
}
|
||||
private fun checkExpire(): Boolean {
|
||||
val validPeriod = MMKV.defaultMMKV().decodeLong(BZYConstants.Companion.MMKV_KEY_VPN_VALID_PERIOD)
|
||||
if (validPeriod > 0) {
|
||||
val nowTime = System.currentTimeMillis();
|
||||
com.cncat.vpn.common.log.Log.d("validPeriod==>$validPeriod, nowTime==>$nowTime")
|
||||
if (validPeriod >= nowTime) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,17 @@ import android.app.ActivityManager
|
|||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.MediaDrm
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.RequiresApi
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.net.NetworkInterface
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
|
|
@ -18,6 +23,7 @@ import java.security.NoSuchAlgorithmException
|
|||
import java.util.Arrays
|
||||
import java.util.Collections
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
import javax.crypto.Cipher
|
||||
|
|
@ -319,4 +325,56 @@ object Utils {
|
|||
|
||||
return String(decrypted, Charsets.UTF_8)
|
||||
}
|
||||
/**
|
||||
* 是否处于 VPN 网络环境
|
||||
* ✔ API 21+ 可用
|
||||
*/
|
||||
fun isVpnActive(context: Context): Boolean {
|
||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val networks = cm.allNetworks
|
||||
|
||||
for (network in networks) {
|
||||
val caps = cm.getNetworkCapabilities(network) ?: continue
|
||||
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
/**
|
||||
* okhttp版 测试
|
||||
*/
|
||||
|
||||
fun checkHttpOk(url: String, timeoutMs: Long = 3000): Boolean {
|
||||
return try {
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectTimeout(timeoutMs, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(timeoutMs, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.header("Range", "bytes=0-0")
|
||||
.build()
|
||||
|
||||
val response = client.newCall(request).execute()
|
||||
response.isSuccessful || response.code in 300..399
|
||||
} catch (e: Exception) {
|
||||
Log.e("NET_CHECK", "checkHttp failed", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 是否有可用网络(不保证能上网)
|
||||
*/
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun isNetworkAvailable(context: Context): Boolean {
|
||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val network = cm.activeNetwork ?: return false
|
||||
val caps = cm.getNetworkCapabilities(network) ?: return false
|
||||
|
||||
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@color/translate"
|
||||
>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:background="@drawable/activity_bg"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/tvShowContent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/LogingUseing"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/black"
|
||||
android:textStyle="bold"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSure"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvShowContent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:text="@string/AlreadyKnown"
|
||||
android:paddingStart="40dp"
|
||||
android:paddingEnd="40dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:background="@drawable/pay_sure_click_effect"
|
||||
android:textColor="@color/blue_4072FE"
|
||||
android:textSize="16sp"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<string name="http">HTTP</string>
|
||||
<string name="compatible">兼容</string>
|
||||
<string name="format_provider_type">%1$s(%2$s)</string>
|
||||
<string name="clash_logcat">Clash 日志捕捉工具</string>
|
||||
<string name="clash_logcat">一键连 日志捕捉工具</string>
|
||||
<string name="WelcomeBack">欢迎回来</string>
|
||||
<string name="username">用户名</string>
|
||||
<string name="password">密码</string>
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
<string name="QueryingPaymentResults">正在查询支付结果</string>
|
||||
<string name="SuccessfullyUpdatedUserInformation">更新用户信息成功</string>
|
||||
<string name="PaymentResultsStr">未查询到已支付,如果长时间未到账请关闭App重新打开.</string>
|
||||
<string name="QueryFailed">查询失败</string>
|
||||
<string name="QueryFailed">查询用户信息失败</string>
|
||||
<string name="NodeAcquisitionFailed">获取节点失败:</string>
|
||||
<string name="SingInGive">签到成功 获取赠送时长:%d (分钟)</string>
|
||||
<string name="CheckFailed">签到失败</string>
|
||||
|
|
@ -479,9 +479,21 @@
|
|||
<string name="ShartPagetxt">邀请新朋友通过你的分享链接注册并订购任意会员套餐,\n
|
||||
您可以获得30天会员(仅限新用户订购有效)</string>
|
||||
<string name="LinkSharetxt">点击分享</string>
|
||||
<string name="OpenSwitchingSourceModel">请开启加速后在进行全局模式切换</string>
|
||||
<string name="OpenSwitchingSourceModel">请开启加速后在进行模式切换</string>
|
||||
<string name="GolbalModeNoChoose">全局模式加速下暂不支持切换节点,请切换至智能模式!</string>
|
||||
<string name="GetNodesAgined">获取节点中,请稍后点击加速</string>
|
||||
<string name="SubscriptionlinkTop">获取节点信息出错,请手动点击 [源切换] 或联系客服处理!</string>
|
||||
<string name="bindingShowTxt">当前账号尚未绑定,直接退出可能会造成账号丢失,推荐前往绑定账号或保存临时账号后在进行退出</string>
|
||||
<string name="LogingUseing">
|
||||
1.点击「开始采集行为日志」后,请复现无法使用的问题或正常使用App.
|
||||
\n2.在行为日志采集完成后请再次点击「上传采集行为日志」,提示「行为日志上传成功」后可联系客服人员.
|
||||
\n3.若上传中出现失败等相关问题请联系「客服」
|
||||
\n4.感谢您的支持与信任
|
||||
</string>
|
||||
<string name="modelchanging">模式切换中</string>
|
||||
<string name="modelchangSuccess">模式切换成功</string>
|
||||
<string name="BehaviorLogSuccessfully">行为日志上传成功!</string>
|
||||
<string name="BehaviorLogError">行为日志上传失败!</string>
|
||||
<string name="vpnStartingError">VPN权限未允许 请设置允许!</string>
|
||||
<string name="CloseChooseNode">请关闭加速后在切换节点</string>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue