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.util.Log import android.view.View import android.view.WindowManager import android.webkit.JavascriptInterface import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Button 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 import com.baidu.mobads.action.ActionType import com.baidu.mobads.action.BaiduAction import com.baidu.mobads.action.PrivacyStatus import com.cncat.vpn.service.ClashManager import com.cncat.vpn.service.remote.IClashManager import com.cncat.vpn.service.remote.wrap import com.github.gzuliyujiang.oaid.DeviceID import com.github.gzuliyujiang.oaid.DeviceIdentifier import com.google.gson.JsonObject 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.app.MainApplication.Companion.OAID import com.yingmao.clash.base.BaseActivity import com.yingmao.clash.conf.BZYConstants import com.yingmao.clash.conf.BZYConstants.Companion.MMKV_KEY_IS_REGISTER import com.yingmao.clash.conf.BZYConstants.Companion.MMKV_KEY_NOTE_DEFAULT_PSW import com.yingmao.clash.conf.BZYConstants.Companion.MMKV_KEY_REMEMBER_PASSWORD import com.yingmao.clash.conf.BZYConstants.Companion.MMKV_KEY_REMEMBER_USERNAME import com.yingmao.clash.entity.CheckLoginRsp import com.yingmao.clash.listener.DownLoadListener import com.yingmao.clash.net.http.HttpReqType import com.yingmao.clash.net.http.HttpStatus import com.yingmao.clash.net.http.entity.AppVersionCheckRsp import com.yingmao.clash.net.http.entity.LoginRsp import com.yingmao.clash.net.http.entity.RegisterRsp import com.yingmao.clash.util.AESUtil import com.yingmao.clash.util.Utils import com.yingmao.clash.view.RxToast import com.yingmao.clash.view.dialog.DialogAppVersion import com.yingmao.clash.view.dialog.DialogDoMainChoose import com.yingmao.clash.view.dialog.SplashDialog import com.yingmao.clash.vm.SplashAtyVM import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.io.File import java.util.Locale @SuppressLint("CustomSplashScreen") class SplashActivity : BaseActivity() { private lateinit var rlPb: View private lateinit var llAction: View private var butSkip: Button? = null private var butRegister: Button? = null private lateinit var rlMain: RelativeLayout private var webView: WebView? = null private lateinit var pbWeb: ProgressBar private lateinit var splashAtyVM: SplashAtyVM companion object { var registerKey = "" var registerIV2 = "" lateinit var username: String var InviteCode="" } private var captchaInfo = "" private var initErrMsg = "" override fun onCreate(savedInstanceState: Bundle?) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) super.onCreate(savedInstanceState) initErrMsg = resources.getString(R.string.splashErrorTips) 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) webView = findViewById(R.id.webView) pbWeb = findViewById(R.id.pb_web) butSkip = findViewById(R.id.but_skip) butRegister = findViewById(R.id.but_register) val decodeBool = MMKV.defaultMMKV().decodeBool(BZYConstants.is_splash_first, false) if (!decodeBool) { val splashDialog = SplashDialog(this) splashDialog.showDialog(this@SplashActivity) splashDialog.setOnDialogClickListener(object : SplashDialog.ActivityLinkListener { override fun onAgreeClickListener() { MMKV.defaultMMKV().encode(BZYConstants.is_splash_first, true) splashDialog.cancel() rlPb.visibility = View.VISIBLE initInto() } override fun onDisagreeClickListener() { MMKV.defaultMMKV().encode(BZYConstants.is_splash_first, false) splashDialog.cancel() finish() } }) } else { initInto() } } private fun initInto() { serStore.isYM = true initApplication() } private fun initApplication() { username = Utils.getUniqueID(this) MainApplication.initUtil() val oaId = OAID Log.i("SplashActOAID","OAID::"+OAID+"oaId.isEmpty()::"+oaId.isEmpty()) if(oaId.isEmpty()){ if (DeviceID.supportedOAID(this)){ val aynOAID = DeviceIdentifier.getOAID(this) OAID=aynOAID BaiduAction.setOaid(aynOAID) //设置同意隐私政策激活设备 BaiduAction.setPrivacyStatus(PrivacyStatus.AGREE) } }else{ BaiduAction.setOaid(oaId) //设置同意隐私政策激活设备 BaiduAction.setPrivacyStatus(PrivacyStatus.AGREE) } setLocalLanguage() val clashBinder = MainApplication.getApp()?.let { ClashManager(it).wrap() as IClashManager } clashBinder?.let { cb -> val signList = AESUtil.getSingInfo(this@SplashActivity, BuildConfig.applicationId, AESUtil.SHA256) if (signList.isNotEmpty()) { val si = signList[0] val ss1 = cb.getSS1(si) val ss2 = cb.getSS2(si) registerKey = cb.getRegisterKey(si) registerIV2 = cb.getRegisterKiv(si) MainApplication.KEY2 = ss1 MainApplication.IV2 = ss2 val routerSet = MMKV.defaultMMKV().decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST) if (routerSet != null && routerSet.isNotEmpty()) { SplashHelper(splashAtyVM).getHostWithFallback { success -> } isNeedRegister(true) } else { if (BuildConfig.is_back_cn == true) { //没有备用域名所以无需调用getHost isNeedRegister(true) } else { SplashHelper(splashAtyVM).getHostWithFallback { success -> MainScope().launch { if (success) { isNeedRegister(true) } else { isNeedRegister(true) } } } } } } } // //对源地址加密操作 // val byteArrayToHexStr = // AESUtil.byteArrayToHexStr(AESUtil.encrypt("https://api.yijianlianjsq.com+https://yjl.kuaifei.net+https://api.onetouchjsq.com+https://yjlapi.anxuan009.com+https://yjl.jimao.xyz")) // android.util.Log.i("AAAAAAAAAAA","data:::"+byteArrayToHexStr) } @SuppressLint("JavascriptInterface", "SetJavaScriptEnabled") private fun setWebView() { rlPb.visibility = View.INVISIBLE webView?.visibility = View.VISIBLE lifecycleScope.launch(Dispatchers.Main) { delay(3000) // 延迟3秒 com.cncat.vpn.common.log.Log.d("setWebView delay") butSkip?.visibility = View.VISIBLE butRegister?.visibility = View.VISIBLE } // WebView设置 val webSettings = webView?.settings // 可选,设置开启Chrome调试 WebView.setWebContentsDebuggingEnabled(true) // 设置屏幕自适应 webSettings?.useWideViewPort = true webSettings?.loadWithOverviewMode = true // 建议禁止缓存加载,以确保在攻击发生时可快速获取最新的阿里云验证码2.0进行对抗 webSettings?.cacheMode = WebSettings.LOAD_NO_CACHE // 设置WebView组件支持加载JavaScript webSettings?.javaScriptEnabled = true webSettings?.domStorageEnabled = true // 设置不使用默认浏览器,而直接使用WebView组件加载页面 webView?.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { view.loadUrl(url) return true } } webView?.addJavascriptInterface(CaptchaJsInterface(), "CaptchaJsInterface") // 加载业务页面 webView?.loadUrl("file:///android_asset/captcha.html") } inner class CaptchaJsInterface() { @SuppressLint("ClickableViewAccessibility") @JavascriptInterface public fun getVerifyResult(verifyResult: String): Boolean { com.cncat.vpn.common.log.Log.d("verifyResult==>${verifyResult}") captchaInfo = verifyResult launch { webView?.setOnTouchListener { _, _ -> true } pbWeb.visibility = View.VISIBLE } doRegisterByEncryption() return true } } private fun setLocalLanguage() { val defLan = "" val decodeString = MMKV.defaultMMKV().decodeString(BZYConstants.languageInfo, defLan) if (!decodeString.isNullOrEmpty()) { when (decodeString) { "Chinese-CN" -> { val config = resources.configuration config.locale = Locale.CHINA updateLanguage("Chinese-CN", Locale.CHINA) } "Chinese-HK" -> { val config = resources.configuration config.locale = Locale("zh", "HK") updateLanguage("Chinese-HK", Locale("zh", "HK")) } "English" -> { val config = resources.configuration config.locale = Locale.ENGLISH updateLanguage("English", Locale.ENGLISH) } } } } private fun isNeedRegister(isNeedRegister: Boolean) { if (!BuildConfig.is_free && BuildConfig.productId.isNotEmpty()) { //渠道包都自动注册登录,广州22222也是非自动注册 if (MMKV.defaultMMKV().decodeBool(MMKV_KEY_IS_REGISTER, false)) { rlPb.visibility = View.VISIBLE butSkip?.visibility = View.INVISIBLE butRegister?.visibility = View.INVISIBLE splashAtyVM.checkLogin() } else { if (isNeedRegister) { doRegisterByEncryption() } else { setWebView() } } } else {//非渠道自由包 val token = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_TOKEN) llAction.visibility = View.VISIBLE rlPb.visibility = View.INVISIBLE butSkip?.visibility = View.INVISIBLE butRegister?.visibility = View.INVISIBLE if (MMKV.defaultMMKV().decodeBool(MMKV_KEY_IS_REGISTER, false)) { if (token != null && token.isNotEmpty()) { llAction.visibility = View.GONE rlPb.visibility = View.VISIBLE splashAtyVM.checkLogin() } else { val intent = Intent(this@SplashActivity, LoginActivity::class.java) startActivity(intent) finish() } } else { findViewById(R.id.rl_action_login).setOnClickListener {//点有账号直接登录的 val intent = Intent(this@SplashActivity, LoginActivity::class.java) startActivity(intent) finish() } findViewById(R.id.rl_action_register).setOnClickListener {//无账号的自动注册但不会填任何渠道号 val intent = Intent(this@SplashActivity, RegisterActivity::class.java) startActivity(intent) finish() } } } } @Synchronized fun doRegisterByEncryption() { var userAgent = System.getProperty("http.agent") Log.i("SplashRegister", "userAgent::$userAgent") if (userAgent != null) { if (userAgent.isEmpty() || userAgent.length < 5) { val webView = WebView(this@SplashActivity) userAgent = webView.settings.userAgentString } } else { val webView = WebView(this@SplashActivity) userAgent = webView.settings.userAgentString } var oaId = OAID Log.i( "SplashRegister", "OAID::" + OAID + "oaId.isEmpty()::" + oaId.isEmpty() + "userAgent::" + userAgent ) val equipmentModel = Utils.getEquipmentModel() Log.i("equipmentModel", "equipmentModel::" + equipmentModel) if (oaId.isEmpty()) { if (DeviceID.supportedOAID(MainApplication.getApp())) { //同步获取oaid val aOaId = DeviceIdentifier.getOAID(MainApplication.getApp()) oaId = aOaId OAID = aOaId Log.i( "doRegisterByEncryption", "aOaId::" + aOaId + "oaId::" + oaId + "OAID::" + OAID ) } } val reqJson = JsonObject() reqJson.addProperty("password", BZYConstants.defaultPassword) reqJson.addProperty("checkPassword", BZYConstants.defaultPassword) reqJson.addProperty("invitationCode", BuildConfig.productId) reqJson.addProperty("clientIp", "127.0.0.1") reqJson.addProperty("from", "5") reqJson.addProperty("androidDevice", username) reqJson.addProperty("userAgent", userAgent) reqJson.addProperty("oaid", oaId) reqJson.addProperty("equipmentModel", equipmentModel) reqJson.addProperty("pkg", BuildConfig.applicationId) if (captchaInfo.isNotEmpty()) { reqJson.addProperty("captchaVerifyParam", captchaInfo) } if (registerKey.isNotEmpty() && registerIV2.isNotEmpty()) { val data = AESUtil.byteArrayToHexStr( AESUtil.RegisterEncrypt(reqJson.toString(), registerKey, registerIV2) ) val params: Map = mutableMapOf( "data" to data ) splashAtyVM.registerByEncryption(params) } } @SuppressLint("ClickableViewAccessibility") override fun httpSuccess(httpStatus: HttpStatus.Success) { when (httpStatus.reqType) { HttpReqType.REGISTER -> { dialogDoMainChoose?.dismiss() with(httpStatus.rsp as RegisterRsp) { if (code == "10000" || code == "200") { if (data != null) { if (!data.phoneNumber.isNullOrEmpty()) { MMKV.defaultMMKV() .encode(MMKV_KEY_REMEMBER_USERNAME, data.phoneNumber) } else { MMKV.defaultMMKV().encode(MMKV_KEY_REMEMBER_USERNAME, username) } } else { MMKV.defaultMMKV().encode(MMKV_KEY_REMEMBER_USERNAME, username) } MMKV.defaultMMKV() .encode(MMKV_KEY_REMEMBER_PASSWORD, BZYConstants.defaultPassword) MMKV.defaultMMKV().encode(MMKV_KEY_IS_REGISTER, true) MMKV.defaultMMKV().encode(MMKV_KEY_NOTE_DEFAULT_PSW, true) val encryName = AESUtil.byteArrayToHexStr( AESUtil.encrypt(data?.phoneNumber) ) val encryPsw = AESUtil.byteArrayToHexStr( AESUtil.encrypt(BZYConstants.defaultPassword) ) val decodeBool = MMKV.defaultMMKV().decodeBool(BZYConstants.INITIALIZATION, true) if (decodeBool) { MMKV.defaultMMKV().encode(BZYConstants.INITIALIZATION, true) } splashAtyVM.login(encryName, encryPsw) } else if (code == "20062") { message?.let { RxToast.error(it) } if (data != null) { if (!data.phoneNumber.isNullOrEmpty()) { MMKV.defaultMMKV() .encode(MMKV_KEY_REMEMBER_USERNAME, data.phoneNumber) } else { MMKV.defaultMMKV().encode(MMKV_KEY_REMEMBER_USERNAME, username) } } else { MMKV.defaultMMKV().encode(MMKV_KEY_REMEMBER_USERNAME, username) } val intent = Intent(this@SplashActivity, LoginActivity::class.java) startActivity(intent) finish() } else if (code == "403") { pbWeb.visibility = View.INVISIBLE message?.let { RxToast.error(it) } webView?.setOnTouchListener { _, _ -> false } } else { MaterialDialog(this@SplashActivity).show { message(text = initErrMsg) positiveButton(R.string.retry) { doRegisterByEncryption() } negativeButton(R.string.cancel) { it.dismiss() finish() } } } } } HttpReqType.LOGIN -> { val rsp = httpStatus.rsp if (rsp is LoginRsp) { if (rsp.code == "1000") { val mmkv = MMKV.defaultMMKV() com.cncat.vpn.common.log.Log.i(" HttpReqType.LOGIN token ==> ${rsp.data.vpnToken}") mmkv.encode(BZYConstants.MMKV_KEY_TOKEN, rsp.data.vpnToken) mmkv.encode(BZYConstants.MMKV_KEY_PH_TOKEN, rsp.data.phToken) mmkv.encode(BZYConstants.MMKV_KEY_USER_ID, rsp.data.userId) splashAtyVM.checkLogin() } else { MaterialDialog(this@SplashActivity).show { message(text = initErrMsg) positiveButton(R.string.retry) { val entryName = AESUtil.byteArrayToHexStr( AESUtil.encrypt(username) ) val entryPsw = AESUtil.byteArrayToHexStr( AESUtil.encrypt(BZYConstants.defaultPassword) ) splashAtyVM.login(entryName, entryPsw) } negativeButton(R.string.cancel) { it.dismiss() finish() } } } } } HttpReqType.CHECK_LOGIN -> { dialogDoMainChoose?.dismiss() (httpStatus.rsp as CheckLoginRsp).let { checkLoginRsp -> if (checkLoginRsp.code == 0) { com.cncat.vpn.common.log.Log.d("splash CHECK_LOGIN==>") checkLoginRsp.data?.let { MainApplication.setUserInfo(it) } val expired = checkLoginRsp.data?.expired val isPerfect = checkLoginRsp.data?.isPerfect val userCommand = checkLoginRsp.data?.userCommand val logSwitch = checkLoginRsp.data?.logSwitch val mmkv = MMKV.defaultMMKV() mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true) mmkv.encode( BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, checkLoginRsp.data?.vpnExpireTime ?: 0 ) mmkv.encode(BZYConstants.MMKV_KEY_IS_PERFECT, isPerfect ?: true) mmkv.encode(BZYConstants.MMKV_KEY_VPN_UserCommand, userCommand ?: "") mmkv.encode(BZYConstants.MMKV_KEY_VPN_LogSwitch, logSwitch ?: "") toMainPage() } else { toLoginPage() } } } HttpReqType.App_VERION_CHECK -> { (httpStatus.rsp as AppVersionCheckRsp).let { avcr -> com.cncat.vpn.common.log.Log.i("App_VERION_CHECK ==>$avcr") if (avcr.code == 200) { avcr.data?.let { md -> if (md.isForce) { //强制更新 if (!TextUtils.isEmpty(md.versionPath)) { showUpdateDialog(md.versionPath!!, md.illustrate!!, true) } } else if (md.isUpgrade) { //更新 if (!TextUtils.isEmpty(md.versionPath)) { showUpdateDialog(md.versionPath!!, md.illustrate!!, false) } } else { val intent = Intent(this@SplashActivity, MainActivity::class.java) startActivity(intent) finish() } } } else { val intent = Intent(this@SplashActivity, MainActivity::class.java) startActivity(intent) finish() } } } else -> {} } } var dialogDoMainChoose: DialogDoMainChoose? = null override fun httpFailure(httpStatus: HttpStatus.Failure) { when (httpStatus.reqType) { HttpReqType.REGISTER -> { rlPb.visibility = View.GONE pbWeb.visibility = View.INVISIBLE com.cncat.vpn.common.log.Log.d("httpFailure HttpReqType.REGISTER") if (super.switchServer2()) {//切换服务器并直接重试 doRegisterByEncryption() } else { MaterialDialog(this@SplashActivity).show { message(text = initErrMsg) positiveButton(R.string.retry) { doRegisterByEncryption() } negativeButton(R.string.cancel) { it.dismiss() finish() } } } } HttpReqType.LOGIN -> { MaterialDialog(this@SplashActivity).show { message(text = initErrMsg) positiveButton(R.string.confirm) { val encryName = AESUtil.byteArrayToHexStr( AESUtil.encrypt(username) ) val encryPsw = AESUtil.byteArrayToHexStr( AESUtil.encrypt(BZYConstants.defaultPassword) ) splashAtyVM.login(encryName, encryPsw) } negativeButton(R.string.cancel) { it.dismiss() finish() } } } HttpReqType.CHECK_LOGIN -> { com.cncat.vpn.common.log.Log.d("httpFailure HttpReqType.CHECK_LOGIN") RxToast.error("检测到网络异常,请尝试切换其他地址.") if (super.switchServer2()) { splashAtyVM.checkLogin() } else { toLoginPage() } } else -> {} } } private fun toMainPage() { //监测版本是否需要更新 splashAtyVM.appCheck() } private fun toLoginPage() { val intent = Intent(this, LoginActivity::class.java) startActivity(intent) finish() } fun updateLanguage(language: String, locale: Locale) { MMKV.defaultMMKV().encode(BZYConstants.languageInfo, language) val resources = this.resources val metrics = resources.displayMetrics val config = resources.configuration config.locale = locale resources.updateConfiguration(config, metrics) } private fun showUpdateDialog(url: String, illustrate: String, isForce: Boolean) { if (TextUtils.isEmpty(url)) { RxToast.error(resources.getString(R.string.downloadEmpty)) return } var notice = illustrate if (TextUtils.isEmpty(illustrate)) { notice = resources.getString(R.string.NewVersion) } var isDownloaded = false; var isFinish = false var downFilePath = "" val dialog = DialogAppVersion( this, notice, url, isForce, object : DialogAppVersion.OnClickListener { override fun ok(tv_down: TextView, pb_down: ProgressBar) { if (isFinish) { installApk(downFilePath) return } if (isDownloaded) { RxToast.info(resources.getString(R.string.downloading)) return } isDownloaded = true splashAtyVM.downLoadFile(url, object : DownLoadListener { @SuppressLint("SetTextI18n") override fun loading(progress: Int) { lifecycleScope.async { pb_down.progress = progress tv_down.text = resources.getString(R.string.download) + "$progress%" } } override fun finish(path: String) { com.cncat.vpn.common.log.Log.d("down finish path==>$path") isFinish = true downFilePath = path installApk(path) } override fun error(msg: String) { com.cncat.vpn.common.log.Log.d("down error msg==>$msg") } }) } override fun cancel() { val intent = Intent(this@SplashActivity, MainActivity::class.java) startActivity(intent) finish() } }) dialog.show() } fun installApk(filePath: String) { val file = File(filePath) val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { FileProvider.getUriForFile(this, "${applicationContext.packageName}.fileprovider", file) } else { Uri.fromFile(file) } val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(uri, "application/vnd.android.package-archive") flags = Intent.FLAG_ACTIVITY_NEW_TASK if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } } startActivity(intent) } }