yijianlianUI/app/src/main/java/com/yingmao/clash/act/UIMainActivity.kt

685 lines
26 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.yingmao.clash.act
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import com.cncat.vpn.common.log.Log
import com.cncat.vpn.core.model.LogMessage
import com.cncat.vpn.service.ClashManager
import com.cncat.vpn.service.ProfileManager
import com.cncat.vpn.service.model.Profile
import com.cncat.vpn.service.remote.IClashManager
import com.cncat.vpn.service.remote.ILogObserver
import com.cncat.vpn.service.remote.IProfileManager
import com.cncat.vpn.service.remote.wrap
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.orhanobut.logger.Logger
import com.tencent.mmkv.MMKV
import com.yingmao.clash.BuildConfig
import com.yingmao.clash.R
import com.yingmao.clash.app.MainApplication
import com.yingmao.clash.base.BaseActivity
import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.conf.Conf
import com.yingmao.clash.entity.CheckLoginRsp
import com.yingmao.clash.fragment.home.HomeFragment
import com.yingmao.clash.fragment.setting.SettingFragment
import com.yingmao.clash.listener.DownLoadListener
import com.yingmao.clash.net.http.HttpApi
import com.yingmao.clash.net.http.HttpReqType
import com.yingmao.clash.net.http.HttpStatus
import com.yingmao.clash.net.http.NetService
import com.yingmao.clash.net.http.entity.AppVersionCheckRsp
import com.yingmao.clash.net.http.entity.GetActivityRsp
import com.yingmao.clash.net.http.entity.PhDownloadUrlRsp
import com.yingmao.clash.net.http.entity.SubscriptionRsp
import com.yingmao.clash.remote.Remote
import com.yingmao.clash.util.AESUtil
import com.yingmao.clash.util.CommonUtils
import com.yingmao.clash.util.Utils
import com.yingmao.clash.view.RxToast
import com.yingmao.clash.view.dialog.ActivityViewDialog
import com.yingmao.clash.view.dialog.ActivityViewDialog.OnDialogClickListener
import com.yingmao.clash.view.dialog.DialogAppVersion
import com.yingmao.clash.view.dialog.DialogBandChoose
import com.yingmao.clash.view.dialog.DialogNotice
import com.yingmao.clash.view.dialog.RxDialogShapeLoading
import com.yingmao.clash.vm.MainAtyVM
import com.yingmao.clash.webview.BZYWebViewNormal
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Timer
import java.util.TimerTask
import java.util.UUID
class UIMainActivity : BaseActivity() {
private lateinit var bottomNavigationView: BottomNavigationView
private val fragments = arrayListOf<Fragment>()
private lateinit var mainAtyVM: MainAtyVM
private var payResultLoading: RxDialogShapeLoading? = null
private val clashRunning: Boolean
get() = Remote.broadcasts.clashRunning
private var timer: Timer? = null
private var uTimerTask: TimerTask? = null
private var newMainActGlo: Boolean = false
private var homeFragment:HomeFragment?=null
private var settingFragment:SettingFragment?=null
@SuppressLint("SimpleDateFormat")
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
private var activityDialog: ActivityViewDialog? = null
private var checkPayResultDia: MaterialDialog? = null
private var checkBindAccountDia: MaterialDialog? = null
companion object {
//记录心跳失败次数
var HEART_BEAT_FAILURE_FREQUENCY = 0
const val REQUEST_CODE_PAY_WEB_VIEW_ACTIVITY = 1000
var isSubbed = false
var newUuid: String? = null
var subUrl: String? = null
var clashBinder: IClashManager? = null
var profileBinder: IProfileManager? = null
var isUpload: Boolean = false
var hostsMem: ArrayList<String> = ArrayList()
var isSubed = false
var newMainActGlo: Boolean = false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_uimain)
initView(savedInstanceState)
profileBinder =
MainApplication.getApp()?.let { ProfileManager(it).wrap() as IProfileManager }
clashBinder = MainApplication.getApp()?.let { ClashManager(it).wrap() as IClashManager }
mainAtyVM = ViewModelProvider(this)[MainAtyVM::class.java]
observe(this, mainAtyVM)
mainAtyVM.startHeartBeat()
MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false)
mainAtyVM.getAirNodes()
val isGlobal = MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false)
newMainActGlo = isGlobal
startLog()
firstEntry()
}
private fun initView(savedInstanceState: Bundle?) {
bottomNavigationView = findViewById(R.id.bottom_navigation)
if (savedInstanceState == null) {
homeFragment = HomeFragment()
supportFragmentManager.beginTransaction()
.add(R.id.fragmentContainer, homeFragment!!)
.commit()
}
bottomNavigationView.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
if (homeFragment == null) homeFragment = HomeFragment()
showFragment(homeFragment!!)
}
R.id.navigation_setting -> {
if (settingFragment == null) settingFragment = SettingFragment()
showFragment(settingFragment!!)
}
}
true
}
}
private fun loadFragment(fragment: Fragment) {
val fragmentManager: FragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment)
transaction.commit()
}
private fun showFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
// 隐藏所有 Fragment
fragmentManager.fragments.forEach {
transaction.hide(it)
}
if (!fragment.isAdded) {
transaction.add(R.id.fragmentContainer, fragment)
} else {
transaction.show(fragment)
}
transaction.commit()
}
override fun httpLoading(httpStatus: HttpStatus.Loading) {
if (httpStatus.reqType == HttpReqType.HEARTBEAT) {
Logger.d("心跳中")
} else if (httpStatus.reqType == HttpReqType.CHECK_LOGIN) {
showLoadingDialog(resources.getString(R.string.QueryingPaymentResults))
}
}
override fun httpSuccess(httpStatus: HttpStatus.Success) {
when (httpStatus.reqType) {
HttpReqType.GET_AIRNODES -> {
val data = httpStatus.rsp as SubscriptionRsp
if (data.code != "1000") {
RxToast.error(resources.getString(R.string.NodeAcquisitionFailed) + "${data.msg}")
}
data.data.let { url ->
android.util.Log.e("Data", "机场URL=${url}")
// if (url.isNotEmpty()) {
// subUrl = url
// lifecycleScope.launch(Dispatchers.IO) {
// setSub2(url)
// }
// }
if (url.isNotEmpty()) {
//todo 判断订阅地址是否修改过,未修改就用接口返回的地址反之使用本地修改的地址
var pro_sub_url =
MMKV.defaultMMKV().decodeString(BZYConstants.PROFILE_SUB_URL, "")
// Log.d("pro_sub_url==>${pro_sub_url}")
if (!TextUtils.isEmpty(pro_sub_url)){
if (pro_sub_url == url) {
subUrl = url
} else {
subUrl = pro_sub_url
}
}else{
subUrl = url
}
lifecycleScope.launch(Dispatchers.IO) {
// setSub(url)
subUrl?.let { setSub2(it) }
}
}
}
}
HttpReqType.GetActivity -> {
val data = httpStatus.rsp as GetActivityRsp
if (data.code == 200) {
if (data.data.data.list.isNotEmpty()) {
data.data.data.list[0].redirectLink.let {
if(activityDialog==null){
activityDialog =
ActivityViewDialog(this, data.data.data.list[0].redirectLink)
activityDialog?.setOnDialogClickListener(object :
OnDialogClickListener {
override fun onRechargeClickListener() {
//跳转到支付界面
goBackupRechargeWeb()
}
override fun onCancelClickListener() {
activityDialog?.dismiss()
}
})
activityDialog?.show()
}
}
}
}
}
HttpReqType.CHECK_LOGIN -> {
(httpStatus.rsp as CheckLoginRsp).let { checkLoginRsp ->
if (checkLoginRsp.code == 0) {
val mmkv = MMKV.defaultMMKV()
val oldExpireTime =
mmkv.decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, 0)
val nowExpireTime = checkLoginRsp.data!!.vpnExpireTime
if (nowExpireTime > oldExpireTime) {
val expired = checkLoginRsp.data.expired
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true)
mmkv.encode(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, nowExpireTime)
val hf = supportFragmentManager.findFragmentById(R.id.fragmentContainer) as? HomeFragment
val sf = supportFragmentManager.findFragmentById(R.id.fragmentContainer) as? SettingFragment
hf?.setExpireDate()
sf?.setExpireDate()
checkPayResultDia?.dismiss()
mainAtyVM.getAirNodes()
RxToast.info(resources.getString(R.string.SuccessfullyUpdatedUserInformation))
} else {
// RxToast.info(resources.getString(R.string.PaymentResultsStr))
}
} else {
RxToast.info(resources.getString(R.string.QueryFailed))
}
payResultLoading?.dismiss()
}
}
HttpReqType.PHDOWNLOADURL -> {
(httpStatus.rsp as PhDownloadUrlRsp).let {
if (it.code == 1000) {
val lastNotice =
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_NOTICE_READ, "")
val data = it.data
if (data?.notice != null && lastNotice != data.notice) {
val notice = data.notice
val dialog = DialogNotice(this@UIMainActivity, notice, data.downloadUrl)
dialog.setOnReadClickListener(object :
DialogNotice.OnReadClickListener {
override fun onReadClick() {
MMKV.defaultMMKV()
.encode(BZYConstants.MMKV_KEY_NOTICE_READ, data.notice)
MMKV.defaultMMKV().encode(
BZYConstants.MMKV_KEY_NOTICE_DOWNLOAD_READ, data.downloadUrl
)
dialog.dismiss()
}
})
dialog.show()
}
}
}
}
else -> {
}
}
}
override fun httpFailure(httpStatus: HttpStatus.Failure) {
when (httpStatus.reqType) {
HttpReqType.HEARTBEAT -> {
Logger.d("心跳失败")
if (5 <= HEART_BEAT_FAILURE_FREQUENCY) {
HEART_BEAT_FAILURE_FREQUENCY = 0
Logger.d("心跳失败次数超过10次退出登录")
cleanUserInfo()
}
HEART_BEAT_FAILURE_FREQUENCY++
}
HttpReqType.CHECK_LOGIN -> {
payResultLoading?.dismiss()
RxToast.error(resources.getString(R.string.QueryFailed))
}
HttpReqType.GET_AIRNODES -> {
Logger.e("获取节点异常:${httpStatus.msg}")
}
HttpReqType.VIPSIGN -> {
RxToast.error(resources.getString(R.string.CheckFailed) + httpStatus.msg)
}
else -> {
}
}
}
private fun showLoadingDialog(msg: String) {
payResultLoading = RxDialogShapeLoading(this)
payResultLoading!!.setMessage(msg)
payResultLoading!!.show()
}
private fun cleanUserInfo() {
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_PH_TOKEN)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_TOKEN)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_CURRENT_NODE)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.NODE_SELECTED_NAME_KEY)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_VPN_UserCommand)
MMKV.defaultMMKV().removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY)
Log.d("cleanUserInfo clashRunning==>${clashRunning}")
if (clashRunning) {
fragments.filterIsInstance<HomeFragment>().forEach { it.stopClash() }
}
stopUploadTimer()
mainAtyVM.stopHeartBeat()
cleanProfile()
startActivity(Intent(CommonUtils.getApp(), LoginActivity::class.java))
finish()
}
fun startUploadTimer() {
if (timer == null) {
timer = Timer()
uTimerTask = object : TimerTask() {
override fun run() {
uploadLogHttp()
}
}
}
timer?.schedule(uTimerTask, 60000, 60000)
}
fun stopUploadTimer() {
timer?.cancel()
timer = null
uTimerTask?.cancel()
uTimerTask = null
}
@OptIn(DelicateCoroutinesApi::class)
private fun cleanProfile() {
Log.d("cleanProfile start")
GlobalScope.launch(Dispatchers.IO) {
profileBinder?.let { pm ->
pm.queryAll().forEach { profile ->
Log.d("cleanProfile go delete name==>${profile.name}, uuid==>${profile.uuid}")
pm.delete(profile.uuid)
}
} ?: let {
Log.d("profileBinder is null")
}
}
}
private suspend fun setSub2(url: String) {
profileBinder?.let { pm ->
pm.queryAll().forEach { itemPro ->
if (itemPro.name != Conf.getClashConfName(this@UIMainActivity.applicationContext)) {
pm.delete(itemPro.uuid)
}
}
val pro = pm.queryActive()
pro?.let {
pm.setActive(it)
delay(500)
//判断配置文件source
if (!TextUtils.isEmpty(pro.source)) {
Log.d("setSub2 pro.source==>${pro.source}, url==>${url}")
//订阅链接不同就更换
if (pro.source != url) {
pm.patch(
pro.uuid, pro.name,
url, 1800000L
)
pm.commit(pro.uuid) { status ->
android.util.Log.d(
Conf.TAG,
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
)
}
}
}
pm.update(it.uuid)
newUuid = pro.uuid.toString()
isSubbed = true
} ?: run {
val uuid: UUID = pm.create(
Profile.Type.Url, Conf.getClashConfName(this@UIMainActivity.applicationContext)
)
delay(500)
pm.queryByUUID(uuid)?.let { qPro ->
pm.setActive(qPro)
pm.patch(
qPro.uuid, qPro.name,
url, 1800000L
)
pm.commit(qPro.uuid) { status ->
android.util.Log.d(
Conf.TAG,
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
)
}
newUuid = qPro.uuid.toString()
isSubbed = true
}
}
}
}
private fun startLog() {
launch(Dispatchers.IO) {
val observer = object : ILogObserver {
override fun newItem(log: LogMessage) {
val msg = log.message
if (BuildConfig.DEBUG) {
Log.i("startLog msg==>${msg}")
}
val domain = Utils.getDomainNameFromString(msg)
if (!TextUtils.isEmpty(domain)) {
addOrSendHost(domain!!)
}
}
}
clashBinder?.let {
it.setLogObserver(observer)
}
}
}
fun addOrSendHost(host: String) {
Log.d("hostsMem size==>${hostsMem.size}")
if (!hostsMem.contains(host) && !isApiHost(host)) {
hostsMem.add(host + "{" + dateFormat.format(Date()))
}
}
private fun isApiHost(host: String): Boolean {
val mmkv = MMKV.defaultMMKV()
var isApiHost = false
val originalUrl =
mmkv.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, BuildConfig.service_url)
val routerHost = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST, HashSet())
if (originalUrl?.contains(host) == true) {
isApiHost = originalUrl.contains(host)
return isApiHost
}
routerHost?.forEach { rh ->
isApiHost = rh.contains(host)
}
return isApiHost
}
private fun firstEntry() {
val decodeBool =
MMKV.defaultMMKV().decodeBool(BZYConstants.INITIALIZATION, false)
if (decodeBool) {
MaterialDialog(this).show {
cornerRadius(res = R.dimen.dp_10)
customView(
R.layout.first_entry_layout,
scrollable = false,
noVerticalPadding = true,
horizontalPadding = false
)
getCustomView().findViewById<TextView>(R.id.determine).setOnClickListener {
MMKV.defaultMMKV().encode(BZYConstants.INITIALIZATION, false)
dismiss()
}
}
} else {
mainAtyVM.getNoticeInfo()
activityPopupWindow()
}
}
private fun activityPopupWindow() {
mainAtyVM.getActivity()
}
fun goRechargeWeb() {
val mmkv = MMKV.defaultMMKV()
val a = mmkv.decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")
val b = mmkv.decodeString(BZYConstants.MMKV_KEY_USER_ID, "")
val c = mmkv.decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")
val intent = Intent(this@UIMainActivity, BZYWebViewNormal::class.java)
val rcURL = NetService.getCurrentUrl() + "/78zccgy0nsgknh9n4/recharge?a=$a&b=$b&c=$c&d=2"
Log.d("rcURL==>${rcURL}")
intent.putExtra("url", rcURL)
startActivityForResult(intent, REQUEST_CODE_PAY_WEB_VIEW_ACTIVITY)
}
public fun goCheckLogin() {
mainAtyVM.checkLogin()
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_PAY_WEB_VIEW_ACTIVITY) {
if (activityDialog != null) {
activityDialog?.dismiss()
}
goCheckLogin()
showBindAccount()
}
}
override fun onDestroy() {
if (activityDialog != null) {
if(activityDialog!!.isShowing){
activityDialog!!.dismiss()
}
}
super.onDestroy()
stopUploadTimer()
mainAtyVM.stopHeartBeat()
}
private fun showPayResultDialog() {
checkPayResultDia = MaterialDialog(this).show {
this.cancelable(false)
cornerRadius(res = R.dimen.dp_10)
customView(
R.layout.payconfirm1_dialog_layout,
scrollable = false,
noVerticalPadding = true,
horizontalPadding = false
)
getCustomView().findViewById<TextView>(R.id.tv_pay_finish).setOnClickListener {
mainAtyVM.checkLogin()
showBindAccount()
dismiss()
}
getCustomView().findViewById<TextView>(R.id.tv_pay_cancle).setOnClickListener {
dismiss()
showBindAccount()
}
}
}
private fun showBindAccount() {
val userAccount =
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
if (Utils.isEmail(userAccount) || Utils.isPhoneNumber(userAccount)) {
return
}
checkBindAccountDia = MaterialDialog(this).show {
this.cancelable(false)
cornerRadius(res = R.dimen.dp_10)
customView(
R.layout.bind_account_dialog_layout,
scrollable = false,
noVerticalPadding = true,
horizontalPadding = false
)
getCustomView().findViewById<TextView>(R.id.tv_pay_finish).setOnClickListener {
//跳转到绑定邮箱界面
showBingDialogChoose()
dismiss()
}
getCustomView().findViewById<TextView>(R.id.tv_pay_cancle).setOnClickListener {
dismiss()
}
}
}
private fun showBingDialogChoose() {
val dialog = DialogBandChoose(this@UIMainActivity)
dialog.setOnBandClickListener(object : DialogBandChoose.OnBandClickListener {
override fun onBandClick(isPhone: Boolean) {
val intent = Intent(CommonUtils.getApp(), BandPhoneActivity::class.java)
intent.putExtra("isPhone", isPhone)
startActivity(intent)
}
})
dialog.show()
}
@OptIn(DelicateCoroutinesApi::class)
fun uploadLogHttp() {
if (hostsMem.size <= 0) {
return
}
if (isUpload) {
return
}
isUpload = true
GlobalScope.launch {
try {
val hostsJsonArray = JsonArray()
for (hostItem in hostsMem) {
val hostAndTime = hostItem.split("{")
val hostJsonObject = JsonObject()
hostJsonObject.addProperty(
"id", MMKV.defaultMMKV().decodeInt(BZYConstants.MMKV_KEY_NOTE_NODE_ID, 0)
)
hostJsonObject.addProperty("requestHosts", hostAndTime[0])
hostJsonObject.addProperty("requestTime", hostAndTime[1])
hostsJsonArray.add(hostJsonObject)
}
val httpApi = NetService.instances.createHttps(HttpApi::class.java)
val hostsJsonArrayStr = hostsJsonArray.toString()
val hostsJsonArrayStrObject = JsonObject()
hostsJsonArrayStrObject.addProperty("requestLogs", hostsJsonArrayStr)
val phToken = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")
hostsJsonArrayStrObject.addProperty("phToken", phToken)
val encryptHostsJsonArrayStrObject = AESUtil.byteArrayToHexStr(
AESUtil.encrypt(hostsJsonArrayStrObject.toString())
)
val requestBody: RequestBody = encryptHostsJsonArrayStrObject
.toRequestBody("text/plain".toMediaTypeOrNull())
val uploadRequestLogs = httpApi.uploadRequestLogs(requestBody)
Log.d("uploadRequestLogs==>${uploadRequestLogs}")
} catch (e: Exception) {
e.printStackTrace()
} finally {
hostsMem.clear()
isUpload = false
}
}
}
fun goBackupRechargeWeb() {
startActivityForResult(
Intent(this@UIMainActivity, MemberRechargeActivity::class.java),
REQUEST_CODE_PAY_WEB_VIEW_ACTIVITY
)
}
}