package com.gh.vspace

import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.sqlite.SQLiteDiskIOException
import android.database.sqlite.SQLiteFullException
import android.net.Uri
import android.os.Build
import android.text.TextUtils
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ProcessLifecycleOwner
import com.gh.ad.AdDelegateHelper
import com.gh.common.constant.Config
import com.gh.common.exposure.ExposureUtils
import com.gh.common.history.HistoryHelper
import com.gh.common.util.*
import com.gh.common.util.NewFlatLogUtils
import com.gh.download.DownloadManager
import com.gh.download.PackageObserver
import com.gh.download.simple.DownloadListener
import com.gh.download.simple.DownloadMessageHandler
import com.gh.download.simple.SimpleDownloadManager
import com.gh.gamecenter.*
import com.gh.gamecenter.category2.CategoryV2Activity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.discovery.DiscoveryActivity
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.ApkEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.InstallGameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.utils.SentryHelper
import com.gh.gamecenter.game.columncollection.detail.ColumnCollectionDetailActivity
import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity
import com.gh.gamecenter.gamecollection.mine.MyGameCollectionActivity
import com.gh.gamecenter.history.HistoryActivity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.mygame.MyGameActivity
import com.gh.gamecenter.packagehelper.PackageFilterManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.subject.SubjectActivity
import com.gh.gamecenter.tag.TagsActivity
import com.gh.gamecenter.va.VCore
import com.gh.gamecenter.va.provider.IVa
import com.gh.gamecenter.va.remote.InstalledAppInfo
import com.gh.gamecenter.va.remote.VaInstallResult
import com.gh.gamecenter.wrapper.ToolbarWrapperActivity
import com.gh.vspace.Constants.*
import com.gh.vspace.db.VGameDatabase
import com.gh.vspace.db.VGameEntity
import com.gh.vspace.gapps.GAppsDownloadDialogFragment
import com.halo.assistant.HaloApp
import com.lg.download.DownloadError
import com.lg.download.DownloadStatus
import com.lg.download.httpclient.DefaultHttpClient
import com.lg.ndownload.DownloadConfigBuilder
import com.lg.vspace.VirtualAppManager
import com.lg.vspace.bridge.BuildConfig
import com.lg.vspace.remote.BinderPool
import com.lg.vspace.remote.listener.RemoteConnectListener
import com.lg.vspace.remote.model.AppInstallerInfo
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.AppManager
import com.lightgame.utils.Utils
import com.lightgame.view.CheckableLinearLayout
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import org.greenrobot.eventbus.EventBus
import java.io.File
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
import java.util.regex.Pattern
import kotlin.collections.set


object VHelper {

    // 畅玩游戏是否已被使用过
    private const val KEY_V_IS_USED = "v_is_used"
    private const val KEY_LAST_PLAYED_TIME = "last_played_time"
    private const val KEY_LAST_ALERT_64_UPDATE_URL = "last_alert_64_update_url"
    private const val KEY_LAST_ALERT_32_UPDATE_URL = "last_alert_32_update_url"
    private const val KEY_TOTAL_PLAYED_TIME = "total_played_time"

    // 新增畅玩启动次数
    private const val KEY_LAUNCH_COUNT = "launch_count"

    private const val G_GMS_PACKAGE_NAME = "com.google.android.gms"
    private const val G_VENDING_PACKAGE_NAME = "com.android.vending"
    private const val G_GSF_PACKAGE_NAME = "com.google.android.gsf"

    const val KEY_REQUIRED_G_APPS = "required_g_apps"
    const val G_APPS_DOWNLOAD_ID = "d673761a1dc031d40afc90d0a6efd25a"
    const val G_APPS_INSTALLED_MD5 = "g_apps_installed_md5"

    const val LOG_TAG = "VSPACE"
    const val DEFAULT_VSPACE_PACKAGENAME = "com.lg.vspace"
    const val VSPACE_32BIT_PACKAGENAME = "$DEFAULT_VSPACE_PACKAGENAME.addon"
    const val VSPACE_32BIT_NEW_PACKAGENAME = com.gh.gamecenter.BuildConfig.EXT_PACKAGE_NAME

    private val mDelegateManager by lazy { VirtualAppManager.get() }
    val mPackageInstalledLiveData = MutableLiveData<String>()
    var mVSpaceLoadingCloseLiveData = MutableLiveData(false)
    private var mLegacyCheckGameInstalled = mutableMapOf<String, Boolean>()
    private var mInstalledInfoList: ArrayList<AppInstallerInfo> = arrayListOf() // 已安装的列表，由畅玩服务返回
    private var mInnerInstalledAppInfoList: List<InstalledAppInfo> = arrayListOf() // 合并包安装的游戏列表
    private var mLastSuccessfullyLaunchedGame: Pair<Long, String>? = null
    private var mShouldShowToast = mutableMapOf<String, Boolean>()

    val vGameDao by lazy { VGameDatabase.instance.vGameDao() }

    @Volatile
    private var mVGameSnapshotList = arrayListOf<VGameEntity>()

    private var m64UpdateEntity: AppEntity? = null
    private var m32UpdateEntity: AppEntity? = null

    private var mIsInitialized = false // 是否已初始化

    private var mIsServiceConnected = false // AIDL 服务是否成功连接，不可作为 AIDL 仍然可用的依据
    private var mShouldLaunchGameAfterInstallation = true // 是否需要在安装完成后启动游戏的开关

    // 当前正卡在安装中的 VA 游戏，避免重复调用安装
    private val mInstallingVaPathSet by lazy { Collections.synchronizedSet(hashSetOf<String>()) }
    private val mLegacyInstallingVaPathSet by lazy { Collections.synchronizedSet(hashSetOf<String>()) }

    // 畅玩服务连接成功前挂起的动作，连接成功后执行
    private var mPendingActionList = CopyOnWriteArrayList<() -> Unit>()

    // 启动畅玩失败次数统计
    private var mStartServiceFailureCount = 0

    // 因为显示下载畅玩弹窗而处于挂起状态的游戏回调，供安装完畅玩组件后自动下载用
    private var mPendingDownloadCallback: (() -> Unit)? = null

    // 供安装完畅玩组件判断畅玩游戏位数使用
    private var mBit = ""

    // 批量安装的 map k: 包名， v: 文件
    private var mBatchInstallMap = hashMapOf<String, File>()
    private var mTriggerPackageName: String = ""

    // 批量安装的监听
    private var mBatchInstallListener: ((isSuccess: Boolean, interrupted: Boolean) -> Unit)? = null

    // 批量安装失败的统计
    private var mBatchInstallFailedCount = 0

    // 应用是否可见
    private var mIsAppVisible = false

    // 下次应用可见时是否需要尝试重连
    private var mShouldReConnectOnVisible = false

    // 是否已经尝试过重连
    private var mHasAlreadyTriedReConnect = false

    // 设备是否支持畅玩功能
    private var mIsDeviceSupportVGame: Boolean? = null

    // 临时的用来临时匹配安装完成时包名对应的游戏 ID 的 Map
    private var mTempPackageNameAndGameIdMap = hashMapOf<String, String>()

    val mainEntranceMap = mutableMapOf<String, String>()
    private var launchLocation: String? = null
    const val callSiteOnInstallComplet = 1
    const val callSiteUninstall = 2
    val callSite = PublishSubject.create<Int>()
    private val mPackageObserver by lazy {
        PackageObserver.PackageChangeListener {
            val vaConfig = Config.getVSettingEntity()?.va ?: return@PackageChangeListener
            val isVSpace32 = it.packageName == vaConfig.arch32?.packageName
            val isVSpace64 = it.packageName == vaConfig.arch64?.packageName

            if (!isVSpace64 && !isVSpace32) return@PackageChangeListener

            // 需要安装32位畅玩时跳过64位畅玩安装成功的回调
            val skip64VSpaceInstalled = mBit == "32" && isVSpace64 && !PackageUtils.isInstalledFromAllPackage(
                HaloApp.getInstance(),
                vaConfig.arch32?.packageName
            )

            if (it.type == EBPackage.TYPE_INSTALLED) {
                SensorsBridge.trackEvent("HaloFunInstallDone", "space_schema_type", if (isVSpace32) "32位" else "64位")
                if (isVSpace32) {
                    SensorsBridge.trackEvent("HaloFunExpandInstallDone")
                }

                if (skip64VSpaceInstalled) return@PackageChangeListener

                // 帮用户启动因为没有安装畅玩组件而没法进行的下载任务
                // 因为有可能回调时还在后台，但还要弹窗什么的，所以可能会闪退？先 try catch 一下看看
                try {
                    mPendingDownloadCallback?.invoke()
                } catch (e: Exception) {
                    SentryHelper.onEvent("AUTO_DOWNLOAD_VGAME_ERROR", "error_digest", e.localizedMessage)
                }

                mPendingDownloadCallback = null
                mBit = ""
            }

            if (!isVIsUsed()) return@PackageChangeListener

            if (it.type == EBPackage.TYPE_INSTALLED) {
                if (skip64VSpaceInstalled) return@PackageChangeListener

                // 即时调用大概率调不起来，我也不知道为什么，只能延迟大法了
                AppExecutor.uiExecutor.executeWithDelay({
                    connectService()
                }, 500)
            } else if (it.type == EBPackage.TYPE_UNINSTALLED) {
                if (isVSpace64) {
                    // 执行卸载逻辑
                    mIsServiceConnected = false
                    mInstalledInfoList.clear()
                }
            } else if (it.type == EBPackage.TYPE_REPLACED) {
                connectService()
            }
        }
    }


    val va: IVa by lazy { VCore.getInstance() }

    /**
     * 初始化
     */
    @SuppressLint("CheckResult")
    @JvmStatic
    fun init() {
        VArchiveHelper.init()
        onInit()
        AppExecutor.ioExecutor.execute {
            refreshVGameSnapshot()
            updateInnerInstalledAppList()
        }
    }

    private fun onInit() {
        if (isVGameOn()) {
            if (!mIsInitialized) {
                mIsInitialized = true

                if (isVIsUsed()) {
                    connectService(shouldCheckUpdate = true)
                }

                PackageObserver.registerPackageChangeChangeListener(mPackageObserver)

                // 注册应用可见事件监听
                ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
                    override fun onStart(owner: LifecycleOwner) {
                        super.onStart(owner)

                        mIsAppVisible = true

                        if (mShouldReConnectOnVisible) {
                            mShouldReConnectOnVisible = false
                            connectService(shouldConnectSilently = true)
                        }
                    }

                    override fun onStop(owner: LifecycleOwner) {
                        super.onStop(owner)

                        mIsAppVisible = false
                    }
                })
            }
        }
    }

    @JvmStatic
    fun checkVspaceUpdate(context: Context) {

        if (!isVGameOn()) return

        val config = Config.getVSettingEntity()?.va

        if (config?.arch64 != null
            && m64UpdateEntity == null
            && PackageUtils.isInstalledFromAllPackage(context, config.arch64.packageName)
        ) {
            // 检查畅玩助手 64 位组件更新
            getVSpaceUpdate(config.arch64, true)
        }

        if (config?.arch32 != null
            && m32UpdateEntity == null
            && PackageUtils.isInstalledFromAllPackage(context, config.arch32.packageName)
        ) {
            // 检查畅玩助手 32 位组件更新
            getVSpaceUpdate(config.arch32, false)
        }
    }

    /**
     * 手动刷新畅玩游戏快照列表
     */
    @WorkerThread
    @Synchronized
    fun refreshVGameSnapshot() {
        try {
            mVGameSnapshotList = ArrayList(vGameDao.getAll())
        } catch (e: SQLiteDiskIOException) {
            ToastUtils.toast("磁盘出现异常，请稍后再试")
            e.printStackTrace()
        }
    }

    /**
     * 如果断连了，尝试静静重连
     */
    fun reconnectServiceIfNeeded() {
        if (!mIsServiceConnected) {
            connectService(shouldConnectSilently = true)
        }
    }

    /**
     * 连接服务
     */
    fun connectService(
        shouldCheckUpdate: Boolean = false,
        shouldConnectSilently: Boolean = false,
        callbackClosure: (() -> Unit)? = null
    ) {
        Utils.log(LOG_TAG, "尝试连接 V 服务")
        val isOldCwInstalled = PackageUtils.isInstalledFromAllPackage(
            HaloApp.getInstance().applicationContext,
            BuildConfig.AIDL_SERVER_PACKAGE_NAME
        )
        if (!isOldCwInstalled) {
            Utils.log(LOG_TAG, "分体式畅玩没有安装")
            return
        }
        if (!shouldConnectSilently) {
            toastIfServiceConnectTimeout()
        }

        if (callbackClosure != null) {
            mPendingActionList.add(callbackClosure)
        }

        mDelegateManager.connectService(object : RemoteConnectListener {
            override fun onServiceConnectionSuccessed() {
                Utils.log(LOG_TAG, "V 服务连接成功")

                mStartServiceFailureCount = 0
                mShouldReConnectOnVisible = false
                mHasAlreadyTriedReConnect = false
                onConnectionCompleted(shouldCheckUpdate = shouldCheckUpdate)
            }

            override fun onServiceConnectionFailed(failCode: Int) {
                Utils.log(LOG_TAG, "V 服务连接失败")

                // 已断开
                mIsServiceConnected = false

                // 断开时清空正在安装列表
                mLegacyInstallingVaPathSet.forEach {
                    mInstallingVaPathSet.remove(it)
                }
                mLegacyInstallingVaPathSet.clear()

                if (failCode == BinderPool.CONNECT_STATE_NOT_INSTALLED) {
                    Utils.log(LOG_TAG, "未安装畅玩助手")
                }

                VirtualAppManager.get().resetService()

                if (!mHasAlreadyTriedReConnect) {
                    Utils.log(LOG_TAG, "执行断线重连")

                    mHasAlreadyTriedReConnect = true

                    if (mIsAppVisible) {
                        connectService(shouldConnectSilently = true)
                    } else {
                        mShouldReConnectOnVisible = true
                    }
                }
            }

            override fun onStartServiceFailed(failCode: Int) {
                Utils.log(LOG_TAG, "V 服务启动失败")
                if (++mStartServiceFailureCount % 3 == 0) {
                    val config = Config.getVSettingEntity()?.va
                    runOnUiThread {
                        PackageLauncher.launchApp(HaloApp.getInstance(), packageName = config?.arch64?.packageName)
                    }
                }
            }
        })
    }

    /**
     * 连接成功回调
     * @param shouldCheckUpdate 是否需要检查畅玩游戏更新
     */
    fun onConnectionCompleted(shouldCheckUpdate: Boolean = false) {
        mIsServiceConnected = true
        SPUtils.setBoolean(KEY_V_IS_USED, true)

        runOnIoThread {
            // 更新已安装列表
            updateInstalledList()

            //执行等待连接前的事件,注意是在子线程
            for (action in mPendingActionList) {
                action.invoke()
                mPendingActionList.remove(action)
            }

            if (shouldCheckUpdate) {
                checkUpdateViaPackageRepository()
            }
        }
    }

    @JvmStatic
    fun postOnInitialized(callback: () -> Unit) {
        if (mIsServiceConnected) {
            callback()
        } else {
            connectService(shouldCheckUpdate = false, shouldConnectSilently = false, callbackClosure = callback)
        }
    }

    private fun toastIfServiceConnectTimeout(timeout: Long = 3000L) {
        if (!mIsServiceConnected) {
            AppExecutor.uiExecutor.executeWithDelay({
                if (!mIsServiceConnected) {
                    ToastUtils.toast("允许启动畅玩助手才能继续游戏哦~ ")
                }
            }, timeout)
        }
    }

    /**
     * 畅玩助手是否已安装
     */
    fun isVSpaceInstalled(context: Context): Boolean {
        return PackageUtils.isInstalledFromAllPackage(context, VirtualAppManager.AIDL_SERVER_PACKAGE_NAME)
    }

    /**
     * 显示下载畅玩空间弹窗
     */
    fun showVSpaceDialog(context: Context, gameEntity: GameEntity?) {
        VSpaceDialogFragment.showDownloadDialog(
            context,
            appEntity64 = getVSpaceDownloadEntity(true),
            appEntity32 = getVSpaceDownloadEntity(false),
            gameEntity = gameEntity
        )
    }

    /**
     * 获取数据库里所有的畅玩游戏
     *
     * (查询数据库，请在工作线程调用)
     */
    @WorkerThread
    fun getAllVGame(): List<VGameEntity> = vGameDao.getAll()

    /**
     * 获取库里全部畅玩游戏列表的内存快照
     */
    fun getAllVGameSnapshots(): ArrayList<VGameEntity> = ArrayList(mVGameSnapshotList)

    fun getAllInstalledVGameEntity(): ArrayList<InstallGameEntity> = getAllVGameSnapshots().map {
        InstallGameEntity().apply {
            gameId = it.downloadEntity.gameId
            gameIcon = it.downloadEntity.icon
            gameName = it.downloadEntity.getMetaExtra(Constants.GAME_NAME)
            gameVersion = it.downloadEntity.versionName
            packageName = it.packageName
            installTime = mInstalledInfoList.find { info ->
                info.packageName == it.packageName
            }?.firstInstallTime ?: 0L
        }
    }.toArrayList()

    /**
     * 获取内存里的畅玩游戏快照
     *
     * @param gameId 游戏 ID
     * @param packageName 需要获取的畅玩游戏的包名
     *
     */
    @JvmStatic
    fun getVGameSnapshot(gameId: String? = null, packageName: String? = null): VGameEntity? {
        return mVGameSnapshotList.find {
            it.packageName == packageName && (gameId.isNullOrEmpty() || it.downloadEntity.gameId == gameId)
        }
    }

    /**
     * 更新已安装的畅玩列表
     */
    private fun updateInstalledList() {
        mInstalledInfoList = getInstalledList()
        mLegacyCheckGameInstalled.clear()
    }

    private fun updateInnerInstalledAppList(removePackageName: String? = null) {
        synchronized(mInnerInstalledAppInfoList) {
            va.getInstalledApps(mInnerInstalledAppInfoList).let {
                if (it != mInnerInstalledAppInfoList) {
                    mInnerInstalledAppInfoList = it
                }
                // 排除因为卸载导致可能没有真正删除的包
                removePackageName?.let {
                    mInnerInstalledAppInfoList =
                        mInnerInstalledAppInfoList.filter { it.packageName != removePackageName }
                }
            }
        }
    }

    /**
     * 此语义是判断是否是安装的畅玩游戏
     * 畅玩组件是否已安装此包名的畅玩游戏, 包含 1. 分体式畅玩， 2. 一体式畅玩 两个地方
     * 否则应该单独针对分体式和一体式判断
     */
    @JvmStatic
    fun isInstalled(packageName: String?) = isInnerInstalled(packageName) || isLegacyInstalled(packageName)

    /**
     * 分体式畅玩是否安装了游戏
     */
    @JvmStatic
    fun isLegacyInstalled(packageName: String?): Boolean {
        var isInstalled = mInstalledInfoList.any { it.packageName == packageName }
        // 有可能 mInstalledInfoList 获取异常，导致判断为未安装，手动调用 AIDL 再查一次
        if (!isInstalled
            && mIsServiceConnected
            && mDelegateManager.isConnectAidlInterface
        ) {
            packageName?.let {
                try {
                    if (mLegacyCheckGameInstalled[it] != true) {
                        isInstalled = mDelegateManager.checkGameInstalled(packageName)
                        Utils.log(LOG_TAG, "手动调用 AIDL 获取安装情况 -> $isInstalled")
                        mLegacyCheckGameInstalled[it] = true
                    }
                } catch (e: RuntimeException) {
                    Utils.log(LOG_TAG, "手动调用 AIDL 获取安装情况异常 ${e.localizedMessage}")
                }
            }
        }

        return isInstalled
    }

    @JvmStatic
    fun isInnerInstalled(packageName: String?): Boolean {
        return mInnerInstalledAppInfoList.any { it.packageName == packageName }
    }

    /**
     * 启动成功，五秒内退出才显示反馈弹框
     */
    fun showFeedbackDialogIfLastSuccessfulLaunchedGameExitUnexpectedly(activity: AppCompatActivity) {
        if (isVGameOn()) {
            val time = mLastSuccessfullyLaunchedGame?.first
            val packageName = mLastSuccessfullyLaunchedGame?.second
            if (activity !is SplashScreenActivity
                && time != null
                && packageName != null
                && System.currentTimeMillis() - time < 5000
                && !VFeedbackSuppressedSimpleDao().contains(packageName)
            ) {
                getVGameSnapshot(null, packageName)?.let {
                    VFeedbackDialogFragment.show(activity, toGameEntity(it.downloadEntity))
                    mLastSuccessfullyLaunchedGame = null
                }
            }
        }
    }

    /**
     * 获取游戏占用的空间
     */
    fun getAppOccupiedSpace(packageName: String): Long {
        return if (isInnerInstalled(packageName)) {
            va.getAppOccupiedSpace(packageName)
        } else {
            try {
                mDelegateManager.getAppOccupiedSpace(packageName)
            } catch (e: Exception) {
                e.printStackTrace()
                0
            }
        }
    }

    /**
     * 在执行 callback 前先检查组件是否已安装，是否可下载
     */
    fun validateVSpaceBeforeAction(
        context: Context,
        packageName: String?,
        gameEntity: GameEntity?,
        callback: () -> Unit
    ) {
        if (isLegacyInstalled(packageName)) {
            oldCwValidateVSpaceBeforeAction(context, gameEntity, callback)
        } else {
            newCwValidateVspaceBeforeAction(context, gameEntity, callback)
        }
    }

    private fun oldCwValidateVSpaceBeforeAction(context: Context, gameEntity: GameEntity?, callback: () -> Unit) {
        if (showDialogIfVSpaceIsNeeded(
                context,
                gameEntity?.id ?: "",
                gameEntity?.name ?: "",
                gameEntity?.categoryChinese ?: "",
                gameEntity?.gameBit ?: ""
            )
        ) {
            mBit = gameEntity?.gameBit ?: ""
            mPendingDownloadCallback = callback
            return
        }

        val vaConfig64 = Config.getVSettingEntity()?.va?.arch64
        val vaConfig32 = Config.getVSettingEntity()?.va?.arch32
        val installed64SpaceVersionCode = PackageUtils.getVersionCodeByPackageName(vaConfig64?.packageName)
        val installed32SpaceVersionCode = PackageUtils.getVersionCodeByPackageName(vaConfig32?.packageName)

        // 检查更新
        val contains64Update = shouldShowVSpaceUpdate(m64UpdateEntity, installed64SpaceVersionCode)
        val contains32Update = if (gameEntity?.gameBit == "32") shouldShowVSpaceUpdate(
            m32UpdateEntity,
            installed32SpaceVersionCode
        ) else false

        var containsRelated64Update = false

        if (contains32Update) {
            // 检查关联64位是否需要更新
            if (m32UpdateEntity?.relation != null) {
                containsRelated64Update =
                    shouldShowVSpaceUpdate(m32UpdateEntity?.relation, installed64SpaceVersionCode, true)
            }
        }

        if (contains32Update || contains64Update) {
            val gameBit = gameEntity?.gameBit
            val updateEntity = if (gameBit == "32" && contains32Update) m32UpdateEntity else m64UpdateEntity
            val dialogType = if (updateEntity!!.isAlertEveryTime()) "强制更新" else "提示更新"
            NewFlatLogUtils.logHaloFunUpdateDialogShow(
                gameId = gameEntity?.id ?: "",
                gameName = gameEntity?.name ?: "",
                gameArchitecture = if (gameBit == "32") "32位" else "64位",
                targetVaVersion = updateEntity.version ?: ""
            )
            SPUtils.setString(
                if (updateEntity.category == "32-bit") KEY_LAST_ALERT_32_UPDATE_URL else KEY_LAST_ALERT_64_UPDATE_URL,
                updateEntity.url + updateEntity.alert
            )

            val appEntity64 =
                if (gameBit == "32" && contains32Update && m32UpdateEntity?.relation != null) {
                    // 32位游戏且包含32位更新和32位关联64位更新
                    // 存在64位单独更新时对比32位关联的64位更新与64位单独更新推送的版本号
                    if (contains64Update && (m64UpdateEntity?.versionCode
                            ?: 0) > (m32UpdateEntity?.relation?.versionCode ?: 0)
                    ) {
                        // 64位单独更新版本号更高时使用64位单独更新
                        m64UpdateEntity
                    } else if (containsRelated64Update) {
                        // 不存在64位单独更新或32位关联64位更新版本号更高
                        // 对比已安装版本号和32位关联的64位更新版本号，32位关联的64位更新版本号更高时使用32位关联的64位更新
                        m32UpdateEntity?.relation
                    } else {
                        null
                    }
                } else if ((gameBit != "32" && contains64Update) || (gameBit == "32" && contains64Update && !contains32Update)) {
                    // 64位游戏且包含64位单独更新 / 32位游戏无32位更新且包含64位更新
                    m64UpdateEntity
                } else {
                    null
                }
            val appEntity32 = if (contains32Update) updateEntity else null
            val keyArchitecture =
                if (appEntity64 != null && appEntity32 != null) "32位&64位" else if (appEntity64 != null) "64位" else "32位"
            val keyTargetVaVersion = appEntity64?.version ?: appEntity32?.version ?: ""
            DialogHelper.showDialog(
                context = context,
                title = "服务工具更新提示",
                content = updateEntity.content.toString(),
                cancelText = "立即更新",
                confirmText = "继续游戏",
                cancelClickCallback = {
                    NewFlatLogUtils.logHaloFunUpdateDialogClick(
                        dialogType,
                        "立即更新",
                        keyArchitecture,
                        keyTargetVaVersion
                    )
                    VSpaceDialogFragment.showDownloadDialog(
                        context,
                        appEntity64,
                        appEntity32,
                        gameEntity,
                        autoDownload = true,
                        isUpdate = true
                    )
                },
                confirmClickCallback = {
                    NewFlatLogUtils.logHaloFunUpdateDialogClick(
                        dialogType,
                        "继续游戏",
                        keyArchitecture,
                        keyTargetVaVersion
                    )
                    callback.invoke()
                },
                extraConfig = DialogHelper.Config(centerTitle = true),
                uiModificationCallback = {
                    if (updateEntity.isAlertEveryTime()) {
                        it.confirmTv.visibility = View.GONE
                        it.cancelTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
                    }

                    it.confirmTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
                }
            )
            return
        }

        callback.invoke()
    }

    /**
     * 判断是否需要安装32位组件，64位直接callback
     */
    fun newCwValidateVspaceBeforeAction(context: Context, gameEntity: GameEntity?, callback: () -> Unit) {
        val bit = gameEntity?.gameBit ?: ""
        if (bit == "32") {
            val vaArch32 = Config.getVNewSettingEntity()?.va?.arch32
            if (vaArch32 == null) {
                ToastUtils.toast("畅玩助手空间暂未上线")
                return
            }
            val new32UpdateEntity = Config.getNew32UpdateEntity()
            val is32VSpaceInstalled = PackageUtils.isInstalledFromAllPackage(context, vaArch32.packageName)
            if (!is32VSpaceInstalled) {
                val appEntity = AppEntity()
                if (new32UpdateEntity != null && (new32UpdateEntity.versionCode > vaArch32.versionCode)) {
                    appEntity.versionCode = new32UpdateEntity.versionCode
                    appEntity.version = new32UpdateEntity.version
                    appEntity.url = new32UpdateEntity.url
                } else {
                    appEntity.versionCode = vaArch32.versionCode
                    appEntity.version = vaArch32.versionName
                    appEntity.url = vaArch32.url
                }
                VSpace32NewDialogFragment.showDownloadDialog(
                    context,
                    appEntity,
                    gameId = gameEntity?.id ?: "",
                    gameName = gameEntity?.name ?: "",
                )
                return
            }

            if (new32UpdateEntity != null && (PackageUtils.getVersionCodeByPackageName(vaArch32.packageName) < new32UpdateEntity.versionCode)) {
                val dialogType = if (new32UpdateEntity.isForce) "强制更新" else "提示更新"
                NewFlatLogUtils.logHaloFunUpdateDialogShow(
                    gameId = gameEntity?.id ?: "",
                    gameName = gameEntity?.name ?: "",
                    gameArchitecture = "32位",
                    targetVaVersion = new32UpdateEntity.version ?: ""
                )
                DialogHelper.showDialog(
                    context = context,
                    title = "服务工具更新提示",
                    content = new32UpdateEntity.content.toString(),
                    cancelText = "立即更新",
                    confirmText = if (new32UpdateEntity.isForce) "取消" else "继续游戏",
                    cancelClickCallback = {
                        NewFlatLogUtils.logHaloFunUpdateDialogClick(
                            dialogType,
                            "立即更新",
                            "32位",
                            new32UpdateEntity.version ?: ""
                        )
                        VSpaceUpdate32NewDialogFragment.showDownloadDialog(
                            context,
                            new32UpdateEntity,
                            gameEntity,
                            autoDownload = true,
                            isUpdate = true
                        )
                    },
                    confirmClickCallback = {
                        NewFlatLogUtils.logHaloFunUpdateDialogClick(
                            dialogType,
                            "继续游戏",
                            "32位",
                            new32UpdateEntity.version ?: ""
                        )
                        if (!new32UpdateEntity.isForce) {
                            callback.invoke()
                        }
                    },
                    extraConfig = DialogHelper.Config(centerTitle = true),
                    uiModificationCallback = {
                        if (new32UpdateEntity.isAlertEveryTime()) {
                            it.confirmTv.visibility = View.GONE
                            it.cancelTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
                        }

                        it.confirmTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
                    }
                )
                return
            }
        }
        callback.invoke()
    }

    /**
     * 获取已安装的包名列表
     */
    private fun getInstalledPackageList(): ArrayList<String> {
        val installedList = mInstalledInfoList
        val installedPackageList = arrayListOf<String>()

        for (info in installedList) {
            info.packageName?.let {
                installedPackageList.add(info.packageName)
            }
        }

        Utils.log(LOG_TAG, "获取已安装包名列表 $installedPackageList")

        return installedPackageList
    }

    /**
     * 获取已安装列表信息 (每次调用都会生成一份新的副本，放心使用)
     */
    @WorkerThread
    fun getInstalledList(): ArrayList<AppInstallerInfo> {
        var list: List<AppInstallerInfo>

        try {
            list = mDelegateManager.installedGamesInfo

            Utils.log(LOG_TAG, "获取已安装应用数量，数量为 ${list.size}")
        } catch (e: Exception) {
            // 异常时使用缓存数据
            list = mInstalledInfoList

            Utils.log(LOG_TAG, "获取已安装应用数量异常 ${e.localizedMessage}")
        }

        return ArrayList(list)
    }

    /**
     * 安装新游戏
     * 正常的游戏使用这个方法来安装，内部有重试处理
     */
    fun install(
        context: Context,
        downloadEntity: DownloadEntity,
        isManualInstall: Boolean = false
    ) {
        Utils.log(LOG_TAG, "尝试安装新游戏 ${downloadEntity.path}")
        // 如果一个游戏存在旧版畅玩助手内，此次安装就是游戏更新
        val isLegacyGame = isLegacyInstalled(downloadEntity.packageName)
        Utils.log(
            LOG_TAG,
            "${downloadEntity.packageName} ${if (isLegacyGame) "是旧畅玩助手的游戏" else "是新畅玩组件的游戏"}"
        )
        // 更新此包名对应的 gameId Map
        mTempPackageNameAndGameIdMap[downloadEntity.packageName] = downloadEntity.gameId

        // 当且仅当
        // 1. 全局安装完成启动游戏开关打开
        // 2. 手动触发的安装
        // 3. 不需要谷歌框架或者已经安装好谷歌框架
        // 才启用安装完成后自动启动游戏
        val shouldLaunchGameAfterInstallation: Boolean
        if (isLegacyGame) {
            shouldLaunchGameAfterInstallation =
                mShouldLaunchGameAfterInstallation
                        && (!mIsServiceConnected || isManualInstall)
                        && (!isGAppsRequired(downloadEntity) || isGAppsInstalledInCwLegacy())
        } else {
            shouldLaunchGameAfterInstallation =
                mShouldLaunchGameAfterInstallation
                        && isManualInstall
                        && (!isGAppsRequired(downloadEntity) || isGAppsInstalled())
        }
        // 恢复安装完启动游戏开关
        mShouldLaunchGameAfterInstallation = true
        mShouldShowToast[downloadEntity.packageName] = shouldLaunchGameAfterInstallation
        val installClosure: () -> Unit = {
            if (shouldLaunchGameAfterInstallation) {
                runOnUiThread {
                    val gameEntity = toGameEntity(downloadEntity)
                    try {
                        mVSpaceLoadingCloseLiveData.value = false
                        mPackageInstalledLiveData.value = ""
                        context.startActivity(VSpaceLoadingActivity.getIntent(context, gameEntity, true))
                    } catch (e: Exception) {
                        ToastUtils.toast(e.localizedMessage ?: "启动游戏异常，请稍候再试")
                    }
                }
            }

            val path = downloadEntity.path
            // 正在安装中，忽略重复调用 (手动安装时强行再安装，避免安装结果没有回调时永远卡住，无法安装)
            if (!mInstallingVaPathSet.contains(path)) {
                try {
                    mInstallingVaPathSet.add(path)

                    if (isLegacyGame) {
                        mLegacyInstallingVaPathSet.add(path)
                        runOnUiThread {
                            val intent = VirtualAppManager.getInstallIntent(context, path, downloadEntity.packageName)

                            Utils.log(LOG_TAG, "正在安装 ${downloadEntity.packageName}")
                            context.startActivity(intent)
                        }
                    } else {
                        runOnIoThread {
                            val cwBaseLogParams = va.getCwBaseLogParams(
                                gid = HaloApp.getInstance().gid,
                                oaid = HaloApp.getInstance().oaid,
                                hostVersion = com.gh.gamecenter.BuildConfig.VERSION_NAME,
                                hostChannel = HaloApp.getInstance().channel
                            )
                            val start = System.currentTimeMillis()
                            val gameId = downloadEntity.gameId
                            // 开始安装游戏
                            va.logBeforeGameLaunch(mutableMapOf<String, String>().apply {
                                put(KEY_EVENT, "game_install_began")
                                put(KEY_GAME_ID, gameId)
                                put(KEY_PACKAGE_NAME, downloadEntity.packageName)
                                putAll(cwBaseLogParams)
                            })
                            SensorsBridge.trackEvent("GameInstallBegan", json {
                                "game_name" to downloadEntity.name
                                "game_id" to gameId
                            })

                            val installResult = va.installPackage(path)

                            val timeCost = (System.currentTimeMillis() - start) / 1000L
                            if (installResult.status == VaInstallResult.STATUS_SUCCESS) {
                                // 安装游戏完成
                                va.logBeforeGameLaunch(mutableMapOf<String, String>().apply {
                                    put(KEY_EVENT, "game_install_complete")
                                    put(KEY_GAME_ID, gameId)
                                    put(KEY_PACKAGE_NAME, downloadEntity.packageName)
                                    put("install_time", "$timeCost")
                                    putAll(cwBaseLogParams)
                                })
                                SensorsBridge.trackEvent("GameInstallComplete", json {
                                    "game_name" to downloadEntity.name
                                    "game_id" to gameId
                                    "install_time" to "$timeCost"
                                })
                            } else {
                                // 安装游戏失败
                                va.logBeforeGameLaunch(mutableMapOf<String, String>().apply {
                                    put(KEY_EVENT, "game_install_fail")
                                    put(KEY_GAME_ID, downloadEntity.gameId)
                                    put(KEY_PACKAGE_NAME, downloadEntity.packageName)
                                    put(
                                        KEY_CRASH_MESSAGE, "${installResult.errorMessage}"
                                    )
                                    put(KEY_ERROR_CODE, "${installResult.status}")
                                    putAll(cwBaseLogParams)
                                })
                            }
                            synchronized(mInstallingVaPathSet) {
                                mInstallingVaPathSet.remove(path)
                            }
                            synchronized(mLegacyInstallingVaPathSet) {
                                mLegacyInstallingVaPathSet.remove(path)
                            }
                            onInstallFinished(downloadEntity.packageName, installResult.status)
                        }
                    }


                } catch (e: Exception) {
                    ToastUtils.toast(e.localizedMessage ?: "")
                }
            } else {
                Utils.log(LOG_TAG, "$path 正在安装中，此次安装调用无效")
            }
        }

        if (isLegacyGame) {
            connectService {
                if (mDelegateManager.isConnectAidlInterface) {
                    installClosure.invoke()
                } else {
                    connectService(
                        shouldCheckUpdate = false,
                        shouldConnectSilently = false,
                        callbackClosure = installClosure
                    )
                }
            }
        } else {
            installClosure.invoke()
        }
    }

    /**
     * 批量安装
     * 安装完成后不插入数据库，目前应用于谷歌框架安装
     */
    fun batchInstall(context: Context, installMap: HashMap<String, File>, triggerPackageName: String) {
        Utils.log(LOG_TAG, "尝试批量安装 ${installMap.size} 个新应用")
        // 记录触发批量安装的游戏包名
        mTriggerPackageName = triggerPackageName
        mBatchInstallMap = installMap
        mBatchInstallFailedCount = 0
        install(context, installMap.values.first())
    }

    /**
     * 注册批量安装结果回调，值为空时为取消注册
     */
    fun registerBatchInstallListener(listener: ((isSuccess: Boolean, isInterrupted: Boolean) -> Unit)? = null) {
        mBatchInstallListener = listener
    }

    /**
     * 安装新应用
     * 不会作为游戏存储，目前仅应用于谷歌框架
     * 因为是静默安装，所以不检查其它限制条件
     */
    private fun install(context: Context, file: File) {
        Utils.log(LOG_TAG, "尝试安装新应用 ${file.name}")
        val packageName = file.nameWithoutExtension
        val path = file.path
        if (!mInstallingVaPathSet.contains(path)) {
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
                    && !PackageUtils.isAppOnForeground(context)
                ) {
                    mBatchInstallListener?.invoke(false, true)
                    Utils.log(LOG_TAG, "应用切换至后台，批量安装被打断")
                    return
                }
                mInstallingVaPathSet.add(path)

                if (isLegacyInstalled(mTriggerPackageName)) {
                    mLegacyInstallingVaPathSet.add(path)
                    val intent = VirtualAppManager.getInstallIntent(context, path, packageName)

                    Utils.log(LOG_TAG, "正在安装 $packageName")
                    context.startActivity(intent)
                } else {
                    runOnIoThread {
                        onInstallFinished(packageName, va.installPackage(path).status)
                    }
                }
            } catch (e: Exception) {
                Utils.log(LOG_TAG, "安装异常，${e.localizedMessage}")
            }
        }
    }

    /**
     * 禁用安装完直接启动游戏 (仅此次安装)
     */
    fun disableLaunchGameAfterInstallation() {
        mShouldLaunchGameAfterInstallation = false
    }

    /**
     * 安装完是否直接启动游戏
     */
    fun shouldLaunchGameAfterInstallation() = mShouldLaunchGameAfterInstallation

    /**
     * 安装完成回调
     */
    fun onInstallFinished(packageName: String, status: Int) {
        if (PackageFlavorHelper.IS_TEST_FLAVOR) {
            callSite.onNext(callSiteOnInstallComplet)
        }
        runOnIoThread {
            val gameId = mTempPackageNameAndGameIdMap[packageName] ?: ""
            val downloadEntity = getVDownloadEntity(gameId = gameId, packageName = packageName)

            if (downloadEntity != null) {

                mInstallingVaPathSet.remove(downloadEntity.path)
                mLegacyInstallingVaPathSet.remove(downloadEntity.path)

                if (status == 0) {
                    // 去掉更新标记
                    downloadEntity.isUpdate = false
                    downloadEntity.addMetaExtra(KEY_LAUNCH_COUNT, "0")
                    try {
                        vGameDao.insert(VGameEntity.from(downloadEntity))
                        refreshVGameSnapshot()
                    } catch (e: SQLiteFullException) {
                        ToastUtils.toast("存储空间不足，安装失败")
                        mVSpaceLoadingCloseLiveData.postValue(true)
                        return@runOnIoThread
                    }
                    updateInnerInstalledAppList()
                    if (isLegacyInstalled(packageName)) {
                        // 存在旧版畅玩组件的游戏更新时才更新安装列表了，不再会有新的包安装到旧版的畅玩空间了
                        updateInstalledList()
                    }
                    PackageObserver.onPackageChanged(
                        EBPackage(EBPackage.TYPE_INSTALLED, packageName, "unknown").also {
                            it.gameId = downloadEntity.gameId
                            it.isVGame = true
                        }
                    )
                    runOnUiThread {
                        showVGameInstalledDialog(downloadEntity)
                    }
                    mPackageInstalledLiveData.postValue(packageName)
                } else {
                    if (mShouldShowToast[packageName] == true) {
                        ToastUtils.toast("启动异常，请重新启动游戏试试")
                    }
                    mVSpaceLoadingCloseLiveData.postValue(true)
                }
                mShouldShowToast.remove(packageName)
            } else {
                // downloadEntity 为空，可能是框架类的安装 （走另外一个下载管理）
                if (status == 0) {
                    if (mIsServiceConnected) {
                        updateInstalledList()
                    }
                    updateInnerInstalledAppList()
                }
            }

            // 更新已安装的任务，执行其它等待安装的任务
            if (mBatchInstallMap.size != 0) {
                val iterator = mBatchInstallMap.iterator()
                var nextFileToInstall: File? = null
                while (iterator.hasNext()) {
                    val keySet = iterator.next()
                    if (keySet.key == packageName) {
                        // 从待安装列表里移除已安装完成的任务
                        iterator.remove()
                        val path = keySet.value.path
                        mInstallingVaPathSet.remove(path)
                        mLegacyInstallingVaPathSet.remove(path)

                        // 安装失败时更新失败计数
                        if (status != 0) {
                            mBatchInstallFailedCount++
                        }
                    } else {
                        // 包名不一样，将下一个需要安装的包设为它
                        nextFileToInstall = keySet.value
                    }
                }

                if (nextFileToInstall != null) {
                    install(HaloApp.getInstance(), nextFileToInstall)
                }

                if (mBatchInstallMap.size == 0) {
                    runOnUiThread {
                        // 批量安装完成后回调成功/失败事件
                        mBatchInstallListener?.invoke(mBatchInstallFailedCount == 0, false)
                    }
                }
            }
        }
    }

    /**
     * 游戏是否正在安装中
     */
    fun isInstalling(packageName: String): Boolean {
        val gameId = mTempPackageNameAndGameIdMap[packageName] ?: ""
        val downloadEntity = getVDownloadEntity(gameId = gameId, packageName = packageName) ?: return false
        return (mInstallingVaPathSet.contains(downloadEntity.path))
    }

    /**
     * 获取实体 (虽然实体是 DownloadEntity 其实就是游戏实体)
     * 优先从下载管理里获取（根据 gameId 获取）
     * 下载管理不存在时从畅玩游戏数据库的里获取
     * 根据 gameId 找不到的时候，回落到用 packageName 在 DownloadManager 里再找匹配的且是畅玩的
     */
    private fun getVDownloadEntity(gameId: String, packageName: String): DownloadEntity? {
        var matchedEntity = DownloadManager.getInstance().getDownloadEntitySnapshot("", gameId)
            ?: getVDownloadEntitySnapshot(gameId = gameId, packageName = packageName)

        if (matchedEntity == null) {
            val packageNameMatchedEntity =
                DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName)
            if (packageNameMatchedEntity?.asVGame() == true) {
                matchedEntity = packageNameMatchedEntity
            }
        }

        return matchedEntity
    }

    fun installOrLaunch(
        context: Context,
        downloadEntity: DownloadEntity,
        location: String? = null
    ) {
        Utils.log(LOG_TAG, "检测是需要安装还是启动(DownloadEntity) ${downloadEntity.gameId}")

        if (downloadEntity.name.isNullOrEmpty()) {
            SentryHelper.onEvent(
                "V_GAME_DOWNLOAD_ENTITY_NAME_EMPTY",
                "game_id",
                downloadEntity.gameId,
                "location",
                location
            )
        }

        installOrLaunch(
            context,
            downloadEntity.packageName,
            downloadEntity.gameId,
            downloadEntity.name ?: "",
            downloadEntity.getGameCategory(),
            downloadEntity.getMetaExtra(Constants.KEY_BIT),
            location
        )
    }

    @JvmStatic
    fun installOrLaunch(
        context: Context,
        gameEntity: GameEntity,
        location: String? = null
    ) {
        Utils.log(LOG_TAG, "检测是需要安装还是启动(GameEntity) ${gameEntity.id}")

        installOrLaunch(
            context,
            gameEntity.getUniquePackageName() ?: "",
            gameEntity.id,
            gameEntity.name ?: "",
            gameEntity.category ?: "",
            gameEntity.gameBit,
            location
        )
    }

    /**
     * 安装或启动应用
     */
    private fun installOrLaunch(
        context: Context,
        packageName: String,
        gameId: String,
        gameName: String,
        gameType: String,
        bit: String,
        location: String?
    ) {
        launchLocation = location
        Utils.log(LOG_TAG, "检测是需要安装还是启动 $packageName")

        if (location != null) {
            logLaunchButtonClicked(packageName, gameId, gameName, gameType, location)
        }

        validateVSpaceBeforeAction(context, packageName, GameEntity().apply {
            id = gameId
            name = gameName
            downloadStatus = Constants.V_GAME
            category = gameType
            setApk(arrayListOf(ApkEntity(packageName = packageName, bit = bit)))
        }) {
            if (try {
                    va.isAppInstalled(packageName)
                } catch (e: Exception) {
                    isInnerInstalled(packageName)
                } || isLegacyInstalled(packageName)
            ) {
                launch(context, packageName)
                return@validateVSpaceBeforeAction
            }

            // 检查下载管理是否有下载实体，有实体表明未安装成功
            val downloadEntity = getVDownloadEntity(gameId = gameId, packageName = packageName)

            if (downloadEntity != null) {
                val downloadedFile = File(downloadEntity.path)
                if (downloadedFile.exists() && downloadedFile.length() == downloadEntity.size) {
                    install(context, downloadEntity, true)
                } else {
                    // 重新下载
                    updateOrReDownload(downloadEntity, null)
                }
            } else {
                ToastUtils.toast("该游戏已损坏，请重新下载")
            }
        }
    }

    /**
     * 启动应用
     * @param ignoreGApps 忽略谷歌框架
     */
    @JvmStatic
    fun launch(
        context: Context,
        packageName: String,
        ignoreGApps: Boolean = false,
        showLoading: Boolean = true
    ) {
        Utils.log(LOG_TAG, "尝试打开应用 $packageName")
        mVSpaceLoadingCloseLiveData.postValue(true)
        val isLegacyGame = isLegacyInstalled(packageName)
        // 置空下载挂起回调（能 launch 游戏说明畅玩组件已安装）
        mPendingDownloadCallback = null

        val downloadEntity = getVDownloadEntity(gameId = "", packageName = packageName)

        val gameId = downloadEntity?.gameId ?: "unknown"
        val gameName = downloadEntity?.name ?: "unknown"
        val gameIcon = downloadEntity?.icon ?: "unknown"
        // 若需要安装谷歌框架，弹谷歌框架安装弹窗
        if (!ignoreGApps && isGAppsRequired(downloadEntity) && if (isLegacyGame) {
                !isGAppsInstalledInCwLegacy()
            } else {
                !isGAppsInstalled()
            }
        ) {
            // show dialog
            val currentActivity = CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity
                ?: if (context is AppCompatActivity) context else return

            GAppsDownloadDialogFragment
                .getInstance(packageName, gameId, gameName)
                .show(currentActivity.supportFragmentManager, "G_APPS_FRAGMENT")
            return
        }

        try {
            logLaunch(gameId, gameName, downloadEntity?.getGameCategory() ?: "", packageName)

            if (showLoading) {
                val thirdPartyAd = AdDelegateHelper.vGameLaunchAd?.thirdPartyAd
                val intent = mDelegateManager.getStartGameIntent(
                    packageName,
                    gameId,
                    gameName,
                    gameIcon,
                    MetaUtil.getBase64EncodedAndroidId(),
                    HaloApp.getInstance().gid,
                    com.gh.gamecenter.BuildConfig.VERSION_NAME,
                    HaloApp.getInstance().channel,
                    thirdPartyAd?.slotId,
                    thirdPartyAd?.displaySize,
                    com.gh.gamecenter.BuildConfig.VA_VERSION_NAME,
                    HaloApp.getInstance().oaid
                )
                // 覆盖字段
                if (!isLegacyGame) {
                    appendPageSource(context, intent)
                    intent.setComponent(
                        ComponentName(
                            com.gh.gamecenter.BuildConfig.APPLICATION_ID,
                            VirtualAppManager.AIDL_SERVER_REMOTE_GUIDE_ACTIVITY
                        )
                    )
                    if (va.getPluginVersion().isEmpty()) {
                        val url = va.getDefaultPluginUrl()
                        if (url.isEmpty()) {
                            throw IllegalStateException("插件是必须配置的参数，请检查是否配置正确")
                        }
                        // 传递默认的插件信息
                        intent.putExtra("pluginUrl", url)
                    } else {
                        Config.getVNewSettingEntity()?.vaPlugin?.let {
                            intent.putExtra("pluginId", it.id)
                            intent.putExtra("pluginVersionName", it.versionName)
                        }
                    }
                    intent.putExtra(
                        KEY_LAUNCH_COUNT,
                        getVGameSnapshot(null, packageName)?.downloadEntity?.getMetaExtra(KEY_LAUNCH_COUNT)
                            ?.toIntOrNull() ?: 0
                    )
                }
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                context.startActivity(intent)
            } else {
                if (isLegacyGame) {
                    VirtualAppManager.get().launchGame(packageName)
                } else {
                    try {
                        va.launchApp(packageName)
                    } catch (ignore: Exception) {
                    }
                }
            }

            mLastSuccessfullyLaunchedGame = Pair(System.currentTimeMillis(), packageName)

            runOnIoThread {
                updateVGamePlayedTime(packageName, System.currentTimeMillis(), getPlayedTime(packageName))
                if (try {
                        va.isAppInstalled(packageName)
                    } catch (e: Exception) {
                        isInnerInstalled(packageName)
                    } || isLegacyInstalled(packageName)
                ) {
                    val isThisVGameExistInDatabase = getAllVGame().any { it.packageName == packageName }
                    // 存在安装完成任务，但数据没有正常插入到畅玩数据库的情况(磁盘满了？)，遇到这种情况，手动再插一遍
                    if (!isThisVGameExistInDatabase && downloadEntity != null) {
                        vGameDao.insert(VGameEntity.from(downloadEntity))
                        refreshVGameSnapshot()
                    }
                }
            }
            // 启动游戏时也执行下载插件逻辑，修复用户没有下载新的畅玩游戏，插件更新没有触发的问题。
            initVaPlugin(gameId)
        } catch (e: Exception) {
            ToastUtils.toast(e.localizedMessage ?: "")
        }
    }

    /**
     * 启动畅玩事件位置
     */
    private fun appendPageSource(context: Context, intent: Intent) {
        var from = PAGE_SOURCE_OTHERS

        if (launchLocation?.startsWith(PAGE_SOURCE_RECENTLY_PLAYING) == true) {
            // 最近在玩
            from = PAGE_SOURCE_RECENTLY_PLAYING
            launchLocation = null
        } else if (launchLocation == LAUNCH_LOCATION_INSTALLED_LAUNCH_DIALOG) {
            from = PAGE_SOURCE_INSTALLED_LAUNCH_DIALOG
            launchLocation = null
        } else {
            when (context) {
                is MainActivity -> {
                    if (launchLocation == PAGE_SOURCE_SHORTCUT) {
                        // 桌面快捷启动
                        from = PAGE_SOURCE_SHORTCUT
                        launchLocation = null
                    } else {
                        //首页-tab名称
                        //游戏库-tab名称
                        //排行榜
                        context.window.decorView.findViewById<LinearLayout>(R.id.bottomTabContainer)?.let {
                            for (index in 0 until it.childCount) {
                                val childView = it.getChildAt(index)
                                if (childView is CheckableLinearLayout && childView.isChecked) {
                                    childView.findViewById<TextView>(R.id.tabTv)?.text?.let { text ->
                                        val bottomTabName = text.toString()
                                        from = if (bottomTabName == PAGE_SOURCE_RANK) {
                                            bottomTabName
                                        } else {
                                            "${bottomTabName}-${mainEntranceMap[bottomTabName]}"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                is VDownloadManagerActivity -> {
                    // 畅玩管理
                    from = PAGE_SOURCE_CW_MANAGER
                }

                is MyGameActivity -> {
                    // 我的游戏
                    from = PAGE_SOURCE_MY_GAME
                }

                is GameDetailActivity -> {
                    // 游戏详情
                    from = PAGE_SOURCE_GAME_DETAIL
                }

                is SearchActivity -> {
                    // 搜索
                    from = PAGE_SOURCE_SEARCH
                }

                is ColumnCollectionDetailActivity -> {
                    //非首页的排行榜
                    from = PAGE_SOURCE_RANK
                }

                is SubjectActivity -> {
                    // 专题
                    from = PAGE_SOURCE_TOPIC
                }

                is GameCollectionDetailActivity,
                is ToolbarWrapperActivity,
                is BlockActivity,
                is CategoryV2Activity,
                is HistoryActivity,
                is TagsActivity,
                is MyGameCollectionActivity,
                is CollectionActivity,
                is DiscoveryActivity -> {
                    // 可能不全
                    // 二级自定义页面
                    from = PAGE_SOURCE_NON_MAIN
                }
            }
        }
        intent.putExtra(INTENT_EXTRA_KEY_PAGE_SOURCE, from)
        Utils.log(LOG_TAG, "启动畅玩来源：$from")
    }

    /**
     * 记录启动事件
     */
    private fun logLaunch(
        gameId: String,
        gameName: String,
        gameCategory: String,
        packageName: String
    ) {

        val downloadEntity = getVDownloadEntity(gameId = "", packageName = packageName)

        // boundedObject 里找不到游戏类型时，尝试从已安装列表中获取
        val notNullGameCategory = if (gameCategory.isEmpty() && packageName.isNotEmpty()) {
            PackageRepository.gameInstalled.find { it.packageName == packageName }?.category ?: ""
        } else {
            downloadEntity?.getGameCategory() ?: ""
        }

        NewFlatLogUtils.logGameLaunch(
            gameId = gameId,
            gameName = gameName,
            gameCategory = notNullGameCategory,
            downloadStatus = "畅玩"
        )
    }

    /**
     * 记录启动按钮被点击 ()
     */
    fun logLaunchButtonClicked(
        packageName: String,
        gameId: String? = null,
        gameName: String? = null,
        gameCategory: String? = null,
        location: String
    ) {
        val downloadEntity = getVDownloadEntity(gameId = "", packageName = packageName)

        // boundedObject 里找不到游戏类型时，尝试从已安装列表中获取
        val notNullGameCategory = if (gameCategory.isNullOrEmpty() && packageName.isNotEmpty()) {
            PackageRepository.gameInstalled.find { it.packageName == packageName }?.category ?: ""
        } else {
            downloadEntity?.getGameCategory() ?: ""
        }

        NewFlatLogUtils.logGameLaunchButtonClicked(
            gameId = gameId ?: downloadEntity?.gameId ?: "",
            gameName = gameName ?: downloadEntity?.name ?: "",
            location = location,
            gameCategory = notNullGameCategory,
            downloadStatus = "畅玩"
        )
    }

    /**
     * 卸载应用
     */
    fun uninstall(packageName: String?) {
        Utils.log(LOG_TAG, "卸载游戏 $packageName")

        if (packageName.isNullOrBlank()) return
        val isLegacyGame = isLegacyInstalled(packageName)
        val uninstallClosure: () -> Unit = {
            try {

                val result = if (isLegacyGame) {
                    VirtualAppManager.get().uninstallGame(packageName)
                } else {
                    va.unInstallPackage(packageName)
                }
                if (result) {
                    if (isLegacyGame) {
                        updateInstalledList()
                    }
                    updateInnerInstalledAppList(removePackageName = packageName)
                    try {
                        vGameDao.delete(packageName)
                        refreshVGameSnapshot()
                    } catch (e: SQLiteFullException) {
                        e.printStackTrace()
                    }
                }
                if (PackageFlavorHelper.IS_TEST_FLAVOR) {
                    callSite.onNext(callSiteUninstall)
                }
                Utils.log(LOG_TAG, "卸载应用结果 -> $result")
            } catch (e: Exception) {
                ToastUtils.toast(e.localizedMessage ?: "")
            }
        }

        if (isLegacyGame) {
            if (mDelegateManager.isConnectAidlInterface) {
                uninstallClosure.invoke()
            } else {
                connectService(
                    shouldCheckUpdate = false,
                    shouldConnectSilently = false,
                    callbackClosure = uninstallClosure
                )
            }
        } else {
            uninstallClosure.invoke()
        }
    }

    /**
     * 获取畅玩助手版本号
     */
    fun getVersionName(context: Context): String? = try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            context.packageManager.getPackageInfo(
                VirtualAppManager.AIDL_SERVER_PACKAGE_NAME,
                PackageManager.PackageInfoFlags.of(0)
            )
        } else {
            context.packageManager.getPackageInfo(
                VirtualAppManager.AIDL_SERVER_PACKAGE_NAME,
                0
            )
        }.versionName
    } catch (e: PackageManager.NameNotFoundException) {
        e.printStackTrace()
        null
    }

    /**
     * 清除游戏数据
     */
    @JvmStatic
    fun cleanGameData(packageName: String?) {
        Utils.log(LOG_TAG, "清除游戏数据 $packageName")
        if (packageName.isNullOrBlank()) return
        if (isLegacyInstalled(packageName)) {
            val cleanGameDataClosure: () -> Unit = {
                try {
                    val result = VirtualAppManager.get().cleanGameData(packageName)
                    if (result) {
                        updateInstalledList()
                    }
                    Utils.log(LOG_TAG, "清除游戏数据结果 -> $result")
                } catch (e: Exception) {
                    ToastUtils.toast(e.localizedMessage ?: "")
                }
            }
            if (mDelegateManager.isConnectAidlInterface) {
                cleanGameDataClosure.invoke()
            } else {
                connectService(
                    shouldCheckUpdate = false,
                    shouldConnectSilently = false,
                    callbackClosure = cleanGameDataClosure
                )
            }
        } else if (isInnerInstalled(packageName)) {
            va.cleanPackageData(packageName)
        }

    }

    /**
     * 获取游戏最后打开的时间
     * @return 最后打开的时间戳
     */
    fun getLastPlayedTime(downloadEntity: DownloadEntity): Long {
        val lastPlayedTimeString = downloadEntity.getMetaExtra(KEY_LAST_PLAYED_TIME)
        return if (TextUtils.isEmpty(lastPlayedTimeString)) 0 else lastPlayedTimeString.toLong()
    }

    /**
     * 获取游戏的游玩时长
     */
    private fun getTotalPlayedTime(packageName: String): Long {
        return if (isInnerInstalled(packageName)) {
            va.getGameAppTotalTime(packageName)
        } else {
            mInstalledInfoList.firstOrNull { it.packageName == packageName }?.appTotalPlayTime ?: 0L
        }
    }

    /**
     * 根据游戏 ID 和包名获取畅玩游戏的实体快照
     */
    @JvmStatic
    fun getVDownloadEntitySnapshot(gameId: String?, packageName: String?): DownloadEntity? {
        return getVGameSnapshot(gameId, packageName)?.downloadEntity
    }

    /**
     * 更新数据库重中的畅玩游戏
     */
    private fun updateVGamePlayedTime(packageName: String, lastPlayedTime: Long, totalPlayedTime: Long) {
        getVGameSnapshot(null, packageName)?.let {
            it.downloadEntity.addMetaExtra(KEY_LAST_PLAYED_TIME, lastPlayedTime.toString())
            it.downloadEntity.addMetaExtra(KEY_TOTAL_PLAYED_TIME, totalPlayedTime.toString())
            it.downloadEntity.addMetaExtra(
                KEY_LAUNCH_COUNT,
                "${(it.downloadEntity.getMetaExtra(KEY_LAUNCH_COUNT).toIntOrNull() ?: 0) + 1}"
            )
            try {
                vGameDao.insert(it)
                refreshVGameSnapshot()
                PackageRepository.changeRecentVaPlayed()
            } catch (e: SQLiteFullException) {
                ToastUtils.toast("设备存储空间不足，请清理后重试")
            }
        }

        // 更新首页排序
        EventBus.getDefault().post(EBReuse("vgame"))
    }


    private fun showVGameInstalledDialog(downloadEntity: DownloadEntity) {
        val topActivity = AppManager.getInstance().currentActivity() ?: return
        if (topActivity.isFinishing || topActivity is VSpaceLoadingActivity) return

        VGameInstalledLaunchDialog.show(topActivity.fragmentManager, downloadEntity)
    }

    /**
     * 畅玩空间是否已安装
     * 如果已安装或配置为空返回 true
     * 未安装的情况下会弹弹窗
     */
    fun showDialogIfVSpaceIsNeeded(
        context: Context,
        gameId: String,
        gameName: String,
        gameType: String,
        bit: String
    ): Boolean {
        Utils.log(LOG_TAG, "检测是否已安装畅玩空间")

        val vaConfig = Config.getVSettingEntity()?.va

        if (vaConfig == null) {
            ToastUtils.toast("畅玩空间暂未上线")
            Utils.log(LOG_TAG, "畅玩空间暂未上线")
            return true
        }

        val is64VSpaceInstalled = PackageUtils.isInstalledFromAllPackage(context, vaConfig.arch64?.packageName)
        val is32VSpaceInstalled = PackageUtils.isInstalledFromAllPackage(context, vaConfig.arch32?.packageName)
        when {
            !is64VSpaceInstalled -> {
                VSpaceDialogFragment.showDownloadDialog(
                    context,
                    appEntity64 = getVSpaceDownloadEntity(true),
                    appEntity32 = getVSpaceDownloadEntity(false),
                    autoDownload = true,
                    gameId = gameId,
                    gameName = gameName,
                    gameType = gameType,
                    bit = bit
                )
                Utils.log(LOG_TAG, "显示下载64位畅玩空间弹窗")
                return true
            }

            bit == "32" && !is32VSpaceInstalled -> {
                VSpace32DialogFragment.showDownloadDialog(
                    context,
                    getVSpaceDownloadEntity(false),
                    gameId = gameId,
                    gameName = gameName
                )
                Utils.log(LOG_TAG, "显示下载32位畅玩空间弹窗")
                return true
            }
        }

        Utils.log(LOG_TAG, "畅玩空间已安装")
        return false
    }

    private fun getVSpaceDownloadEntity(is64Bit: Boolean): AppEntity {
        val appEntity = AppEntity()
        val vaConfig =
            if (is64Bit) Config.getVSettingEntity()?.va?.arch64 else Config.getVSettingEntity()?.va?.arch32
        appEntity.versionCode = vaConfig?.versionCode ?: 0
        appEntity.version = vaConfig?.versionName
        appEntity.url = vaConfig?.url

        return appEntity
    }

    /**
     * 更新或重下载
     */
    @JvmStatic
    fun updateOrReDownload(gameEntity: GameEntity) {
        PackagesManager.getUpdateList().firstOrNull { it.id == gameEntity.id }
            ?.let { updateEntity ->
                getVGameSnapshot(gameEntity.id, updateEntity.packageName)
                    ?.let { vGame ->
                        updateOrReDownload(vGame.downloadEntity, updateEntity)
                    }
            }
    }

    /**
     * 更新或重下载
     *
     * @param originDownloadEntity 旧的下载实体
     * @param updateEntity 更新内容，当 updateEntity 为空时重新下载
     */
    fun updateOrReDownload(originDownloadEntity: DownloadEntity, updateEntity: GameUpdateEntity? = null) {
        Utils.log(LOG_TAG, "更新应用${originDownloadEntity.packageName}")

        if (updateEntity != null) {
            originDownloadEntity.url = updateEntity.url
            originDownloadEntity.gameId = updateEntity.id
            originDownloadEntity.name = updateEntity.name
            originDownloadEntity.eTag = updateEntity.etag
            originDownloadEntity.icon = updateEntity.icon
            originDownloadEntity.size = 0
            originDownloadEntity.platform = updateEntity.platform
            originDownloadEntity.packageName = updateEntity.packageName
            originDownloadEntity.versionName = updateEntity.version
            originDownloadEntity.isUpdate = true
            // 保存所有游戏标签
            originDownloadEntity.tags = updateEntity.tagStyle.map { it.name }
            originDownloadEntity.addMetaExtra(Constants.RAW_GAME_ICON, updateEntity.rawIcon)
            originDownloadEntity.addMetaExtra(Constants.GAME_ICON_SUBSCRIPT, updateEntity.iconSubscript)
            originDownloadEntity.addMetaExtra(Constants.APK_MD5, updateEntity.md5)
            if (updateEntity.iconFloat != null) {
                originDownloadEntity.addMetaExtra(
                    Constants.GAME_ICON_FLOAT_TOP_TEXT,
                    updateEntity.iconFloat?.upperLeftText
                )
                originDownloadEntity.addMetaExtra(
                    Constants.GAME_ICON_FLOAT_TOP_COLOR,
                    updateEntity.iconFloat?.upperLeftColor
                )
                originDownloadEntity.addMetaExtra(
                    Constants.GAME_ICON_FLOAT_BOTTOM_TEXT,
                    updateEntity.iconFloat?.bottomText
                )
            }

            PackageRepository.removeVGameUpdate(updateEntity.id, true)
        }

        originDownloadEntity.progress = 0
        originDownloadEntity.finalRedirectedUrl = ""
        originDownloadEntity.percent = 0.0

        // 确定下载类型
        val downloadType =
            if (updateEntity == null) ExposureUtils.DownloadType.FUN_DOWNLOAD else ExposureUtils.DownloadType.FUN_UPDATE
        val gameEntity = GameEntity(originDownloadEntity.gameId, originDownloadEntity.name)
        gameEntity.gameVersion = originDownloadEntity.versionName ?: ""
        gameEntity.adGroupId = originDownloadEntity.getMetaExtra(Constants.AD_GROUP_ID)

        val event = ExposureUtils.logADownloadExposureEvent(
            gameEntity,
            originDownloadEntity.platform,
            null,
            downloadType
        )

        originDownloadEntity.exposureTrace = GsonUtils.toJson(event)
        originDownloadEntity.setVGameDownloadModeInDualDownloadMode()

        HistoryHelper.insertGameEntity(gameEntity)
        DownloadManager.getInstance().add(originDownloadEntity, true)

        // 收集下载数据
        DataCollectionUtils.uploadDownload(HaloApp.getInstance(), originDownloadEntity, "开始")

        SensorsBridge.trackEvent(
            "HaloFunGameDownloadClick",
            "game_name", originDownloadEntity.name,
            "game_id", originDownloadEntity.gameId,
            "game_type", originDownloadEntity.categoryChinese,
            "game_schema_type", if (originDownloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位"
        )

        val pushMessageId = (HaloApp.get(Constants.PUSH_MESSAGE_ID, false) as? String) ?: ""
        val pushLinkId = (HaloApp.get(Constants.PUSH_LINK_ENTITY, false) as? LinkEntity)?.link ?: ""
        val isFromPush = pushMessageId.isNotEmpty()

        SensorsBridge.trackEventWithExposureSource(
            "DownloadProcessBegin",
            event.source,
            "game_id", originDownloadEntity.gameId,
            "game_name", originDownloadEntity.name ?: "",
            "game_type", originDownloadEntity.categoryChinese,
            "game_label", originDownloadEntity.tags.joinToString(","),
            "page_name", GlobalActivityManager.getCurrentPageEntity().pageName,
            "page_id", GlobalActivityManager.getCurrentPageEntity().pageId,
            "page_business_id", GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
            "last_page_name", GlobalActivityManager.getLastPageEntity().pageName,
            "last_page_id", GlobalActivityManager.getLastPageEntity().pageId,
            "last_page_business_id", GlobalActivityManager.getLastPageEntity().pageBusinessId,
            "is_ad", if (TextUtils.isEmpty(gameEntity.adGroupId)) "false" else "true",
            "ad_group_id", gameEntity.adGroupId,
            "download_type", "畅玩下载",
            "is_from_push_notifications", isFromPush,
            "message_id", pushMessageId,
            "link_id", pushLinkId,
        )
    }

    /***
     * 实体转换为普通游戏实体
     */
    fun toGameEntity(downloadEntity: DownloadEntity): GameEntity {
        val lastPlayedTimeString = downloadEntity.getMetaExtra(KEY_LAST_PLAYED_TIME)

        return GameEntity(id = downloadEntity.gameId, name = downloadEntity.name).apply {
            setApk(
                arrayListOf(
                    ApkEntity(
                        packageName = downloadEntity.packageName,
                        url = downloadEntity.url,
                        platform = downloadEntity.platform,
                        version = downloadEntity.versionName,
                        size = downloadEntity.getMetaExtra(Constants.APK_SIZE)
                    )
                )
            )
            icon = downloadEntity.icon
            rawIcon = downloadEntity.getMetaExtra(Constants.RAW_GAME_ICON)
            iconSubscript = downloadEntity.getMetaExtra(Constants.GAME_ICON_SUBSCRIPT)
            lastPlayedTime = if (lastPlayedTimeString == "") 0L else lastPlayedTimeString.toLong()
            playedTime = getPlayedTime(downloadEntity.packageName, downloadEntity)
            downloadStatus = Constants.V_GAME
            setEntryMap(DownloadManager.getInstance().getEntryMap(name))
        }
    }

    /**
     * 获取畅玩游戏游玩时长
     */
    private fun getPlayedTime(packageName: String, downloadEntity: DownloadEntity? = null): Long {
        var playedTime = getTotalPlayedTime(packageName)
        Utils.log(LOG_TAG, "获取畅玩游戏游玩时长:${playedTime}")
        val cachedPlayedTimeString = downloadEntity?.getMetaExtra(KEY_TOTAL_PLAYED_TIME)
        if (playedTime == 0L && !cachedPlayedTimeString.isNullOrEmpty()) {
            playedTime = cachedPlayedTimeString.toLong()
        }
        return playedTime
    }

    /**
     * 获取畅玩空间更新
     */
    @SuppressLint("CheckResult")
    private fun getVSpaceUpdate(config: VSetting.VaArch, is64Bit: Boolean) {
        val installedVersionName = PackageUtils.getVersionNameByPackageName(config.packageName)
        val installedVersionCode = PackageUtils.getVersionCodeByPackageName(config.packageName)

        RetrofitManager.getInstance()
            .vApi
            .getPackageUpdate(installedVersionName, installedVersionCode, config.packageName)
            .subscribeOn(Schedulers.io())
            .subscribe(object : BiResponse<AppEntity>() {
                override fun onSuccess(data: AppEntity) {
                    if (is64Bit) m64UpdateEntity = data else m32UpdateEntity = data
                }
            })
    }

    /**
     * 通过 PackageRepository 检查更新
     */
    private fun checkUpdateViaPackageRepository() {
        val rawInstalledPackageList = getInstalledPackageList()
        val validInstalledPackageList = arrayListOf<String>()
        for (packageName in rawInstalledPackageList) {
            if (getVGameSnapshot(null, packageName) != null) {
                validInstalledPackageList.add(packageName)
            }
        }
        PackageRepository.addInstalledGames(PackageFilterManager(), validInstalledPackageList, true)
    }

    /**
     * 是否需要显示畅玩助手更新
     */
    private fun shouldShowVSpaceUpdate(
        updateEntity: AppEntity?,
        installedSpaceVersionCode: Int,
        isRelatedUpdate: Boolean = false
    ): Boolean {
        if (updateEntity == null) return false

        val hasNewerVersion = installedSpaceVersionCode < updateEntity.versionCode

        if (!hasNewerVersion) return false

        // 关联更新忽略检查上次提示更新
        if (isRelatedUpdate) return true

        if (updateEntity.isAlertEveryTime()) return true

        val lastAlertUpdateUrl = updateEntity.url + updateEntity.alert
        if (updateEntity.isAlertOnceADay()
            && SPUtils.getString(if (updateEntity.category == "32-bit") KEY_LAST_ALERT_32_UPDATE_URL else KEY_LAST_ALERT_64_UPDATE_URL) != lastAlertUpdateUrl
        ) {
            return true
        }

        return false
    }

    /**
     * 打开畅玩广场（版块）
     */
    fun startVSpaceSquare(context: Context, exposureEvent: ExposureEvent? = null) {
        val entity =
            SubjectRecommendEntity(link = "62fc8047b07e0c0bb63058c2", nameNormal = "畅玩广场", text = "畅玩广场")
        DirectUtils.directToBlock(context, entity, "", exposureEvent)
    }

    /**
     * 获取未经畅玩处理的原始下载地址
     */
    @Deprecated("不再使用这个后缀来区分游戏，保留只是为了兼容旧数据")
    fun getOriginalUrl(vUrl: String?): String {
        if (vUrl?.endsWith("type=v") == true) {
            return vUrl.removeSuffix("&type=v").removeSuffix("?type=v")
        }
        return vUrl ?: ""
    }

    /**
     * 畅玩功能是否用过
     */
    fun isVIsUsed(): Boolean = SPUtils.getBoolean(KEY_V_IS_USED) || getAllVGameSnapshots().isNotEmpty()

    /**
     * 畅玩功能是否启用
     * 设备系统版本大于 7.1 且后台接口开启时才启用畅玩功能
     *
     * 2024-01-16新增：当处于兼职包特殊渠道（GH_jzcs）时，强行开启畅玩服务
     * https://jira.shanqu.cc/browse/GHZS-4520
     */
    @JvmStatic
    fun isVGameOn() =
        !va.isFakeVa() && isDeviceSupportVGame()
                && (PackageFlavorHelper.IS_TEST_FLAVOR || HaloApp.getInstance().channel == "GH_jzcs" || isVGameOnByConfigSettings())

    /**
     * 设备是否支持畅玩功能
     * 设备系统版本大于 7.1 且支持 64 位
     */
    private fun isDeviceSupportVGame(): Boolean {
        if (mIsDeviceSupportVGame == null) {
            mIsDeviceSupportVGame =
                Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1 && Build.SUPPORTED_ABIS.any { it in Build.SUPPORTED_64_BIT_ABIS }
        }

        return mIsDeviceSupportVGame!!
    }

    /**
     * 后台接口是否启用畅玩功能（若获取不到开关情况时，判断用户是否曾用过畅玩功能）
     */
    private fun isVGameOnByConfigSettings(): Boolean {
        val configSettings = Config.getSettings()

        // 当 settings 为空时，若曾经用过畅玩，也允许用户继续使用畅玩
        return if (configSettings == null) {
            isVIsUsed()
        } else {
            "on" == configSettings.gameSmooth
        }
    }

    fun isRunInExtProcess(packageName: String): Boolean {
        return mInnerInstalledAppInfoList.find { it.packageName == packageName }?.is64bit?.not() ?: false
    }

    /**
     * 检查畅玩游戏是否满足生成存档要求
     */
    fun checkArchiveExist(packageName: String, archiveConfigStr: String): Boolean {
        if (isLegacyInstalled(packageName)) {
            if (!mIsServiceConnected) {
                connectService(shouldConnectSilently = true)
                return false
            }

            return try {
                mDelegateManager.checkArchiveExist(packageName, archiveConfigStr)
            } catch (e: Exception) {
                e.printStackTrace()
                false
            }
        } else {
            return va.checkGameArchiveExists(packageName, archiveConfigStr, isRunInExtProcess(packageName))
        }
    }

    /**
     * 是否需要安装 GApps
     */
    private fun isGAppsRequired(downloadEntity: DownloadEntity?): Boolean {
        return downloadEntity?.getMetaExtra(KEY_REQUIRED_G_APPS) == "on"
    }

    /**
     * 畅玩是否已经成功安装了 GApps
     */
    private fun isGAppsInstalled() = isInnerInstalled(G_GMS_PACKAGE_NAME) && isInnerInstalled(G_GSF_PACKAGE_NAME)
            && isInnerInstalled(G_VENDING_PACKAGE_NAME)

    /**
     * 旧版畅玩是否已经成功安装了 GApps
     */
    private fun isGAppsInstalledInCwLegacy(): Boolean {
        return isLegacyInstalled(G_GMS_PACKAGE_NAME)
                && isLegacyInstalled(G_GSF_PACKAGE_NAME)
                && isLegacyInstalled(G_VENDING_PACKAGE_NAME)
    }

    /**
     * 获取排好序的最近在玩列表
     *
     * 首位：固定展示最近一次点击过下载\启动的游戏
     * 后续位置-优先级：完成下载但未启动过的游戏 > 完成下载且已启动过的游戏
     *         完成下载但未启动过的游戏：按照点击下载的时间倒序排列
     *         完成下载且已启动过的游戏：按照游戏的畅玩时长从左（长）向右（短）排列
     */
    fun getSortedRecentlyPlayedList(): List<VGameItemData> {
        val rawDownloadEntityList = DownloadManager.getInstance().allVDownloadTaskSnapshots
        val rawInstalledEntityList = getAllVGameSnapshots()
        val rawEntityList = arrayListOf<DownloadEntity>()

        var fixedTopEntity: VGameItemData? = null
        val unPlayedEntityList = arrayListOf<VGameItemData>()
        val playedEntityList = arrayListOf<VGameItemData>()
        val sortedEntityList = arrayListOf<VGameItemData>()

        val distinctIdSet = hashSetOf<String>()
        // 将已安装任务的实体放进待排序列表里
        for (rawInstalledEntity in rawInstalledEntityList) {
            distinctIdSet.add(rawInstalledEntity.downloadEntity.gameId)
            rawEntityList.add(rawInstalledEntity.downloadEntity)
        }
        // 将下载任务的实体放进待排序列表里
        for (rawDownloadEntity in rawDownloadEntityList) {
            if (distinctIdSet.contains(rawDownloadEntity.gameId)) {
                continue
            }
            rawEntityList.add(rawDownloadEntity)
        }
        distinctIdSet.clear()

        for (rawEntity in rawEntityList) {
            val lastPlayedTime = getLastPlayedTime(rawEntity)
            val latestModifiedTime = maxOf(rawEntity.start, getLastPlayedTime(rawEntity))

            if (fixedTopEntity == null) {
                fixedTopEntity = VGameItemData.from(rawEntity)
            } else {
                val fixedTopLatestModifiedTime =
                    maxOf(fixedTopEntity.downloadEntity.start, getLastPlayedTime(fixedTopEntity.downloadEntity))
                if (latestModifiedTime > fixedTopLatestModifiedTime) {
                    fixedTopEntity = VGameItemData.from(rawEntity)
                }
            }

            if (lastPlayedTime == 0L) {
                unPlayedEntityList.add(VGameItemData.from(rawEntity))
            } else {
                playedEntityList.add(VGameItemData.from(rawEntity))
            }
        }

        fixedTopEntity?.let {
            sortedEntityList.add(it)
            unPlayedEntityList.removeAll { entity ->
                entity.downloadEntity.packageName == fixedTopEntity.downloadEntity.packageName
            }
            playedEntityList.removeAll { entity ->
                entity.downloadEntity.packageName == fixedTopEntity.downloadEntity.packageName
            }
        }
        sortedEntityList.addAll(unPlayedEntityList.sortedByDescending { it.downloadEntity.start })
        sortedEntityList.addAll(playedEntityList.sortedByDescending { getTotalPlayedTime(it.downloadEntity.packageName) })

        return sortedEntityList.take(8)
    }

    private fun downloadPlugin(id: String, url: String, pluginVersion: String, gameId: String) {
        val pluginFileName = va.getPluginUpdateFileName(id)
        val currentPluginFile = File(HaloApp.getInstance().cacheDir, pluginFileName)
        if (currentPluginFile.exists() && va.isZipFile(currentPluginFile)) {
            return
        } else {
            val currentDownloadEntity = DownloadMessageHandler.findEntity(id)
            if (currentDownloadEntity != null) {
                SimpleDownloadManager.cancel(id)
            }
            val downloadVaPluginBaseLogParams = va.getCwBaseLogParams(
                gid = HaloApp.getInstance().gid,
                oaid = HaloApp.getInstance().oaid,
                hostVersion = com.gh.gamecenter.BuildConfig.VERSION_NAME,
                hostChannel = HaloApp.getInstance().channel
            )
            DownloadMessageHandler.registerListener(id, object : DownloadListener {
                override fun onError(error: DownloadError) {
                    DownloadMessageHandler.unregisterListener(id, this)
                }

                override fun onProgress(progress: Float) {
                }

                override fun onSizeReceived(fileSize: Long) {
                }

                override fun onStatusChanged(status: DownloadStatus) {
                    if (status == DownloadStatus.COMPLETED) {
                        DownloadMessageHandler.unregisterListener(id, this)
                        va.logBeforeGameLaunch(
                            mutableMapOf<String, String>().apply {
                                put(KEY_EVENT, "va_download_complete")
                                put(KEY_GAME_ID, gameId)
                                put("target_va_version", pluginVersion)
                                putAll(downloadVaPluginBaseLogParams)
                            }
                        )
                    }
                }

                override fun onSpeedChanged(speed: Float) {
                }

            })

            SimpleDownloadManager.download(
                DownloadConfigBuilder()
                    .setUniqueId(id)
                    .setFileName(pluginFileName)
                    .setUrl(url)
                    .setPathToStore(HaloApp.getInstance().cacheDir.absolutePath + File.separator)
                    .setHttpClient(DefaultHttpClient())
                    .setDownloadThreadSize(2)
                    .setDownloadListener(DownloadMessageHandler)
                    .setDownloadExecutor(AppExecutor.ioExecutor)
                    .build()
            )
            va.logBeforeGameLaunch(
                mutableMapOf<String, String>().apply {
                    put(KEY_EVENT, "va_download")
                    put(KEY_GAME_ID, gameId)
                    put("target_va_version", pluginVersion)
                    putAll(downloadVaPluginBaseLogParams)
                }
            )
        }
    }


    fun updateAuthorizeInfo(isLogin: Boolean) {
        if (!isVGameOn()) return
        runOnIoThread {
            if (isLogin) {
                val token = UserManager.getInstance().token
                val userName = UserManager.getInstance().userInfoEntity?.name
                val userAvatar = UserManager.getInstance().userInfoEntity?.icon

                if (token == null) {
                    SentryHelper.onEvent("LAUNCH_SUCCESS_BUT_TOKEN_EMPTY", "gid", HaloApp.getInstance().gid)
                    Utils.log(LOG_TAG, "登录成功，但 token 为空")
                    return@runOnIoThread
                }

                Utils.log(
                    LOG_TAG,
                    "登录成功，插入用户信息:token=${token},userName=${userName}, uri=${va.getUriAuthorizationString()}"
                )
                try {
                    HaloApp.getInstance().contentResolver.insert(
                        Uri.parse(va.getUriAuthorizationString()),
                        va.getAuthContentValue(token, userName, userAvatar)
                    )
                } catch (_: Exception) {
                }
            } else {
                Utils.log(LOG_TAG, "登出，删除用户信息")
                try {
                    HaloApp.getInstance().contentResolver.delete(
                        Uri.parse(va.getUriAuthorizationString()),
                        null,
                        null
                    )
                } catch (_: Exception) {
                }
            }
        }
    }

    fun GameEntity?.is32(): Boolean = (this?.gameBit == "32")

    @JvmStatic
    fun shortcutLaunch() {
        launchLocation = PAGE_SOURCE_SHORTCUT
    }

    /**
     * 开始下载畅玩游戏时初始化Va插件
     */
    fun initVaPlugin(gameId: String) {
        Config.getVNewSettingObservable()
            .take(1)
            .observeOn(Schedulers.io())
            .subscribe(object : io.reactivex.Observer<VNewSetting> {
                override fun onSubscribe(d: Disposable) {
                }

                override fun onNext(vNewSetting: VNewSetting) {
                    val pluginVersion64 = va.getPluginVersion()
                    val onlinePluginVersionName = vNewSetting.vaPlugin?.versionName ?: ""
                    val defaultPluginUrl = va.getDefaultPluginUrl()

                    if (pluginVersion64.isEmpty() && onlinePluginVersionName.isEmpty()) {
                        // 不存在64位插件 并且 接口没有获取到插件信息
                        if (defaultPluginUrl.isNotEmpty()) {
                            // 下载默认的插件
                            downloadPlugin(
                                id = MD5Utils.getUrlMD5(defaultPluginUrl),
                                url = defaultPluginUrl,
                                pluginVersion = getPluginVersionInUrl(defaultPluginUrl),
                                gameId = gameId
                            )
                        }
                    }
                    vNewSetting.vaPlugin?.let { vaPlugin ->
                        if (!vaPlugin.id.isNullOrEmpty()) {
                            if (!vaPlugin.url64.isNullOrEmpty()) {
                                downloadPlugin(
                                    id = "${vaPlugin.id}64",
                                    url = vaPlugin.url64,
                                    pluginVersion = vaPlugin.versionName ?: "",
                                    gameId = gameId
                                )
                            }
                            if (!vaPlugin.url32.isNullOrEmpty()) {
                                downloadPlugin(
                                    id = "${vaPlugin.id}32",
                                    url = vaPlugin.url32,
                                    pluginVersion = vaPlugin.versionName ?: "",
                                    gameId = gameId
                                )
                            }
                        }
                    }
                }

                override fun onError(e: Throwable) {
                }

                override fun onComplete() {
                }
            })
    }

    private fun getPluginVersionInUrl(url: String): String {
        val matcher = Pattern.compile("\\d+\\.\\d+\\.\\d+").matcher(url)
        while (matcher.find()) {
            return matcher.group()
        }
        return ""
    }

}