Files
assistant-android/app/src/main/java/com/gh/download/server/BrowserInstallHelper.kt

371 lines
13 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.gh.download.server
import android.content.Context
import android.os.Build
import android.util.Base64
import com.gh.common.constant.Config
import com.gh.common.util.DirectUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.ShellActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.common.loghub.LoghubUtils
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.tryCatchInRelease
import com.gh.gamecenter.core.utils.EmptyCallback
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.NewSettingsEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.google.gson.JsonObject
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.net.URLEncoder
import java.util.*
import kotlin.collections.ArrayList
object BrowserInstallHelper {
private const val PORT = 40705
private const val RESERVE_PORT = 40706
private lateinit var mServer: DownloadServer
private val mFileNameSet by lazy { hashSetOf<String>() }
private val mContext by lazy { HaloApp.getInstance().application }
private var mUseReservePort = false
private var mValidInstalledPackageList: ArrayList<String> = arrayListOf()
private var mValidConditionMatchedCache: Boolean? = null
private fun getServer(port: Int): DownloadServer {
val server = DownloadServer(port)
for (packageName in getAllInstalledPackageList()) {
if (packageName.contains("com.freeme") || packageName.contains("com.zhuoyi")) {
server.isBuggyDevice = true
break
}
}
return server
}
private fun getAllInstalledPackageList(): ArrayList<String> {
when {
mValidInstalledPackageList.isNotEmpty() -> {
return mValidInstalledPackageList
}
else -> {
val allInstalledPackageList = PackageUtils.getAllPackageNameIncludeSystemApps(mContext)
if (allInstalledPackageList.isNotEmpty()) {
mValidInstalledPackageList = allInstalledPackageList
}
return mValidInstalledPackageList
}
}
}
fun downloadFile(filePath: String) {
if (!::mServer.isInitialized) mServer = if (mUseReservePort) getServer(RESERVE_PORT) else getServer(PORT)
if (!mServer.isAlive && !startServer()) {
// 端口占用时使用备用端口重试
mUseReservePort = true
mServer = getServer(RESERVE_PORT)
if (!mServer.isAlive) retryStartServer()
}
var fileName = filePath.substringAfterLast(File.separator)
// 山寨机没有 .apk 后缀但有 Content-Type 管用,还是给它补上 .apk 后缀
if (!mServer.isBuggyDevice) {
fileName = fileName.removeSuffix(DownloadServer.APK_SUFFIX)
}
val downloadUrl =
if (mUseReservePort) "http://127.0.0.1:$RESERVE_PORT/$fileName" else "http://127.0.0.1:$PORT/$fileName"
mFileNameSet.add(fileName)
if (mServer.isBuggyDevice) {
val encodedString =
Base64.encodeToString(URLEncoder.encode(downloadUrl).trim().toByteArray(), Base64.NO_WRAP)
DirectUtils.directToExternalBrowser(
mContext,
"https://down-and.ghzs6.com/redirect?location=base64($encodedString)"
)
} else {
DirectUtils.directToExternalBrowser(mContext, downloadUrl)
}
}
@JvmStatic
fun shouldUseBrowserToInstall(): Boolean {
if (SPUtils.getBoolean(Constants.SP_USE_BROWSER_TO_INSTALL)) {
return true
}
return false
}
@JvmStatic
fun shouldAutoSwitchAssistantInstall(gameEntity: GameEntity): Boolean =
isUseBrowserToInstallEnabled() && shouldUseBrowserToInstall() && gameEntity.isSplitXApk()
/**
* 是否显示使用浏览器来安装弹窗
*/
private fun shouldShowUseBrowserToInstallHint(): Boolean {
return if (isUseBrowserToInstallEnabled()) {
if (SPUtils.getBoolean(Constants.SP_USE_BROWSER_TO_INSTALL)) {
false
} else {
SPUtils.getBoolean(Constants.SP_SHOULD_SHOW_USE_BROWSER_TO_INSTALL_HINT, true)
}
} else {
false
}
}
/**
* 是否显示游戏详情页使用浏览器安装提示
*/
fun shouldShowGameDetailUseBrowserToInstallHint(): Boolean {
return if (isUseBrowserToInstallEnabled()) {
// if (SPUtils.getBoolean(Constants.SP_USE_BROWSER_TO_INSTALL)) {
// false
// } else {
SPUtils.getBoolean(Constants.SP_SHOULD_SHOW_GAMEDETAIL_USE_BROWSER_TO_INSTALL_HINT, true)
// }
} else {
false
}
}
fun hideGameDetailUseBrowserToInstallHint() {
SPUtils.setBoolean(Constants.SP_SHOULD_SHOW_GAMEDETAIL_USE_BROWSER_TO_INSTALL_HINT, false)
}
@JvmStatic
fun showBrowserInstallHintDialog(
context: Context,
gameEntity: GameEntity,
skipBrowserInstallDialog: Boolean = false,
callback: EmptyCallback
) {
if (skipBrowserInstallDialog || !shouldShowUseBrowserToInstallHint()) {
callback.onCallback()
return
}
logOrdinaryBrowserEvent(Type.SWITCH_INSTALL_DIALOG)
SPUtils.setBoolean(Constants.SP_SHOULD_SHOW_USE_BROWSER_TO_INSTALL_HINT, false)
val manufacturer = Build.MANUFACTURER.toUpperCase(Locale.CHINA)
var contentText = "当前安装方式为助手安装,如果出现游戏无法安装的问题,可选择切换安装方式为“浏览器安装”"
if (manufacturer == "OPPO" || manufacturer == "VIVO") {
contentText =
"当前安装方式为助手安装,下载安装游戏需要验证账户密码或指纹。如需免密码安装,可选择切换安装方式为“浏览器安装”"
}
SensorsBridge.trackSwitchInstallDialogShow(
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
gameType = gameEntity.categoryChinese
)
DialogHelper.showDialog(
context,
title = "温馨提示",
content = contentText,
confirmText = "切换安装方式",
cancelText = "继续下载",
confirmClickCallback = {
val intent = ShellActivity.getIntent(context, ShellActivity.Type.SWITCH_INSTALL_METHOD, null)
context.startActivity(intent)
logOrdinaryBrowserEvent(Type.SWITCH_INSTALL_DIALOG_ACCESS)
SensorsBridge.trackSwitchInstallDialogClick(
buttonName = "切换安装方式",
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
gameType = gameEntity.categoryChinese
)
},
cancelClickCallback = {
callback.onCallback()
logOrdinaryBrowserEvent(Type.SWITCH_INSTALL_DIALOG_QUIT)
SensorsBridge.trackSwitchInstallDialogClick(
buttonName = "继续下载",
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
gameType = gameEntity.categoryChinese
)
},
touchOutsideCallback = {
SensorsBridge.trackSwitchInstallDialogClick(
buttonName = "关闭弹窗",
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
gameType = gameEntity.categoryChinese
)
},
extraConfig = DialogHelper.Config(hint = "修改路径:我的光环-设置-切换安装方式")
)
}
/**
* 浏览器安装的后台开关
*/
@JvmStatic
fun isUseBrowserToInstallEnabled(): Boolean {
val settingsEntity = Config.getNewSettingsEntity()
if (settingsEntity == null) {
return false
} else {
if (settingsEntity.installModel?.status == "matched") {
return true
}
return isConditionMatched(settingsEntity)
}
}
/**
* 浏览器安装的后台开关 (返回仅包名匹配时的状态)
*/
@JvmStatic
fun isUseBrowserToInstallEnabledWithPackageMatched(): Boolean {
val settingsEntity = Config.getNewSettingsEntity()
return if (settingsEntity == null) {
false
} else {
isConditionMatched(settingsEntity)
}
}
/**
* 是否满足开启浏览器安装的条件
*/
private fun isConditionMatched(settingsEntity: NewSettingsEntity): Boolean {
if (mValidConditionMatchedCache != null) {
return mValidConditionMatchedCache!!
}
val packageList = getAllInstalledPackageList()
if (packageList.isEmpty()) return false
settingsEntity.installModel?.whiteList?.let {
for (packageName in it) {
if (packageList.contains(packageName)) {
mValidConditionMatchedCache = false
break
}
}
}
settingsEntity.installModel?.packages?.let {
for (packageName in it) {
if (packageList.contains(packageName)) {
mValidConditionMatchedCache = true
break
}
}
}
// 匹配部分字符串即可
settingsEntity.installModel?.regexPackages?.let {
for (packageNamePieces in it) {
for (installedPackageName in packageList) {
if (installedPackageName.contains(packageNamePieces)) {
mValidConditionMatchedCache = true
break
}
}
}
}
return mValidConditionMatchedCache ?: false
}
fun onApkInstalled(path: String?) {
path?.let {
val fileName = path.substringAfterLast(File.separator).removeSuffix(DownloadServer.APK_SUFFIX)
// 更新下载名称 set确定是否需要关闭下载服务
mFileNameSet.remove(fileName)
stopServerIfNeeded()
}
}
private fun startServer(): Boolean {
try {
mServer.start()
} catch (e: Exception) {
return false
}
return true
}
private fun retryStartServer() {
try {
mServer.start()
} catch (e: Exception) {
e.printStackTrace()
Utils.toast(mContext, "浏览器下载服务开启失败")
}
}
private fun stopServerIfNeeded() {
// 下载的文件都下载完了
if (::mServer.isInitialized && mFileNameSet.size == 0) {
mServer.stop()
}
}
/**以下为日志相关方法**/
fun logWebDownloadStarted(exposureEvent: ExposureEvent, downloadId: String, isDownloadComplete: Boolean) {
val jsonObject = JSONObject()
tryCatchInRelease {
jsonObject.put("event", if (isDownloadComplete) "web_download_complete" else "web_download")
jsonObject.put("payload", exposureEvent2JSONObject(exposureEvent.payload, downloadId))
jsonObject.put("meta", LogUtils.getMetaObject())
jsonObject.put("source", JSONArray(GsonUtils.toJson(exposureEvent.source)))
jsonObject.put("timestamp", System.currentTimeMillis() / 1000)
}
Utils.log(jsonObject.toString())
LoghubUtils.log(jsonObject, "event", false)
}
private fun exposureEvent2JSONObject(payload: ExposureEntity, downloadId: String): JSONObject {
val string = (GsonUtils.gson.toJsonTree(payload) as JsonObject).apply {
addProperty("uni_filename", "$downloadId${DownloadServer.APK_SUFFIX}")
}.toString()
return JSONObject(string)
}
fun logOrdinaryBrowserEvent(type: Type) {
val jsonObject = JSONObject()
tryCatchInRelease {
jsonObject.put("event", type.toString().toLowerCase(Locale.CHINA))
jsonObject.put("meta", LogUtils.getMetaObject())
jsonObject.put("timestamp", System.currentTimeMillis() / 1000)
}
Utils.log(jsonObject.toString())
LoghubUtils.log(jsonObject, "event", false)
}
enum class Type {
SWITCH_INSTALL_DIALOG,
SWITCH_INSTALL_DIALOG_QUIT,
SWITCH_INSTALL_DIALOG_ACCESS,
SWITCH_INSTALL_GUIDE_ACCESS,
SWITCH_INSTALL_GUIDE_QUIT,
SWITCH_INSTALL_SETTING,
SWITCH_INSTALL_SETTING_APP,
SWITCH_INSTALL_SETTING_WEB
}
}