package com.gh.vspace

import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.sqlite.SQLiteFullException
import android.net.Uri
import android.os.Build
import android.text.TextUtils
import android.view.View
import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.*
import androidx.lifecycle.Observer
import com.gh.common.constant.Config
import com.gh.common.exposure.ExposureUtils
import com.gh.common.history.HistoryHelper
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.PackageUtils
import com.gh.download.DownloadManager
import com.gh.download.PackageObserver
import com.gh.gamecenter.R
import com.gh.gamecenter.SplashScreenActivity
import com.gh.gamecenter.common.BuildConfig
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.exposure.meta.MetaUtil
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.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.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.retrofit.RetrofitManager
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.vspace.VirtualAppManager
import com.lg.vspace.remote.BinderPool
import com.lg.vspace.remote.listener.RemoteConnectListener
import com.lg.vspace.remote.model.AppInstallerInfo
import com.lg.vspace.remote.model.VGameInstallerResult
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.AppManager
import com.lightgame.utils.Utils
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import java.io.File
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList

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_UPDATE_URL = "last_alert_update_url"
    private const val KEY_TOTAL_PLAYED_TIME = "total_played_time"

    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_DOWNLOAD_DEV_URL =
        "https://dev-and-static.ghzs.com/game/apk/smooth_google/google_play_services.zip"
    const val G_APPS_DOWNLOAD_URL = "https://and-static.ghzs.com/game/apk/smooth_google/google_play_services.zip"

    // 畅玩游戏位数
    const val KEY_BIT = "bit"

    const val LOG_TAG = "VSPACE"
    const val DEFAULT_VSPACE_PACKAGENAME = "com.lg.vspace"
    const val VSPACE_32BIT_PACKAGENAME = "$DEFAULT_VSPACE_PACKAGENAME.addon"
    const val PLATFORM_V = "畅玩版"

    private val mDelegateManager by lazy { VirtualAppManager.get() }
    private val mPackageInstalledLiveData by lazy { MutableLiveData<String>() }
    private var mInstalledInfoList: ArrayList<AppInstallerInfo> = arrayListOf() // 已安装的列表，由畅玩服务返回
    private var mLastSuccessfullyLaunchedGame: Pair<Long, String>? = null

    private val mVGameDao by lazy { VGameDatabase.instance.vGameDao() }
    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 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 mBatchInstallListener: ((isSuccess: Boolean) -> Unit)? = null

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

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

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

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

    val vGameLiveData by lazy { mVGameDao.getAllLiveData() }

    private val mVGameObserver by lazy {
        Observer<List<VGameEntity>> {
            mVGameSnapshotList = ArrayList(it)
        }
    }

    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) {
                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)
                SensorsBridge.trackEvent("HaloFunInstallDone")
            } else if (it.type == EBPackage.TYPE_UNINSTALLED) {
                // 执行卸载逻辑
                mIsServiceConnected = false
                mInstalledInfoList.clear()
            } else if (it.type == EBPackage.TYPE_REPLACED) {
                connectService()
            }
        }
    }

    /**
     * 初始化
     */
    @SuppressLint("CheckResult")
    @JvmStatic
    fun init(context: Context) {
        if (isVGameOn()) {
            if (!mIsInitialized) {
                mIsInitialized = true

                VArchiveHelper.init()

                val config = Config.getVSettingEntity()?.va
                if (config?.arch64 != null
                    && PackageUtils.isInstalledFromAllPackage(context, config.arch64.packageName)
                ) {
                    if (isVIsUsed()) {
                        connectService(shouldCheckUpdate = true)
                    }

                    // 检查畅玩助手64位组件更新
                    getVSpaceUpdate(config.arch64, true)
                }

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

            vGameLiveData.observeForever(mVGameObserver)

            // 注册应用可见事件监听
            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
                }
            })
        }
    }

    /**
     * 手动刷新畅玩游戏快照列表
     */
    @WorkerThread
    fun refreshVGameSnapshot() {
        mVGameSnapshotList = ArrayList(mVGameDao.getAll())
    }

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

    /**
     * 连接服务
     */
    fun connectService(
        shouldCheckUpdate: Boolean = false,
        shouldConnectSilently: Boolean = false,
        callbackClosure: (() -> Unit)? = null
    ) {
        Utils.log(LOG_TAG, "尝试连接 V 服务")

        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

                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 {
                        PackageUtils.launchApplicationByPackageName(HaloApp.getInstance(), 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 {
        val config = Config.getVSettingEntity()?.va
        return config?.arch64 != null && PackageUtils.isInstalledFromAllPackage(context, config.arch64.packageName)
    }

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

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

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

    /**
     * 获取内存里的畅玩游戏快照
     *
     * @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()
    }

    /**
     * 是否已安装此包名的畅玩游戏
     */
    @JvmStatic
    fun isInstalled(packageName: String?): Boolean {
        val isInstalled = mInstalledInfoList.any { it.packageName == packageName }

        Utils.log(LOG_TAG, "$packageName 已安装列表里 -> $isInstalled")
        return isInstalled
    }

    /**
     * 启动成功，五秒内退出才显示反馈弹框
     */
    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 try {
            mDelegateManager.getAppOccupiedSpace(packageName)
        } catch (e: Exception) {
            e.printStackTrace()
            0
        }
    }

    /**
     * 在执行 callback 前先检查组件是否已安装，是否可下载
     */
    fun validateVSpaceBeforeAction(context: Context, gameEntity: GameEntity?, callback: () -> Unit) {
        // 仅下载类型为畅玩的类型才执行判断
        if (gameEntity == null || gameEntity.isVGame()) {
            if (showDialogIfVSpaceIsNeeded(
                    context,
                    gameEntity?.id ?: "",
                    gameEntity?.name ?: "",
                    gameEntity?.getApk()?.get(0)?.bit ?: ""
                )
            ) {
                mBit = gameEntity?.getApk()?.get(0)?.bit ?: ""
                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?.getApk()?.get(0)?.bit == "32") shouldShowVSpaceUpdate(
                m32UpdateEntity,
                installed32SpaceVersionCode
            ) else false

            var containsRelated64Update = false

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

            if (contains32Update || contains64Update) {
                val updateEntity = if (contains64Update) m64UpdateEntity else m32UpdateEntity
                val dialogType = if (updateEntity!!.isAlertEveryTime()) "强制更新" else "提示更新"
                NewFlatLogUtils.logHaloFunEvent("halo_fun_update_dialog_show")
                SPUtils.setString(KEY_LAST_ALERT_UPDATE_URL, updateEntity.url + updateEntity.alert)
                DialogHelper.showDialog(
                    context = context,
                    title = "服务工具更新提示",
                    content = updateEntity.content.toString(),
                    cancelText = "立即更新",
                    confirmText = "继续游戏",
                    cancelClickCallback = {
                        NewFlatLogUtils.logHaloFunUpdateDialogClick(dialogType, "立即更新")
                        val appEntity64 =
                            if (contains32Update && containsRelated64Update) {
                                // 对比32位关联的64位更新与64位单独更新推送的版本号
                                if (contains64Update && (m64UpdateEntity?.versionCode
                                        ?: 0) > (m32UpdateEntity?.relation?.versionCode ?: 0)
                                ) {
                                    m64UpdateEntity
                                } else {
                                    m32UpdateEntity?.relation
                                }
                            } else if (contains64Update) {
                                updateEntity
                            } else {
                                null
                            }
                        val appEntity32 = if (contains32Update) updateEntity else null
                        VSpaceDialogFragment.showDownloadDialog(
                            context,
                            appEntity64,
                            appEntity32,
                            gameEntity,
                            autoDownload = true,
                            isUpdate = true
                        )
                    },
                    confirmClickCallback = {
                        NewFlatLogUtils.logHaloFunUpdateDialogClick(dialogType, "继续游戏")
                        callback.invoke()
                    },
                    extraConfig = DialogHelper.Config(centerTitle = true),
                    uiModificationCallback = {
                        if (updateEntity.isAlertEveryTime()) {
                            it.confirmTv.visibility = View.GONE
                            it.cancelTv.setTextColor(R.color.theme_font.toColor(context))
                        }

                        it.confirmTv.setTextColor(R.color.text_subtitle.toColor(context))
                    }
                )
                return
            }

            callback.invoke()
        } else {
            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}")

        if (showDialogIfVSpaceIsNeeded(
                context,
                downloadEntity.gameId,
                downloadEntity.name,
                downloadEntity.getMetaExtra(KEY_BIT)
            )
        ) return

        // 更新此包名对应的 gameId Map
        mTempPackageNameAndGameIdMap[downloadEntity.packageName] = downloadEntity.gameId

        // 当且仅当
        // 1. 全局安装完成启动游戏开关打开
        // 2. 服务连接不成功或是手动触发的安装
        // 3. 不需要谷歌框架或者已经安装好谷歌框架
        // 才启用安装完成后自动启动游戏
        val shouldLaunchGameAfterInstallation =
            mShouldLaunchGameAfterInstallation
                    && (!mIsServiceConnected || isManualInstall)
                    && (!isGAppsRequired(downloadEntity) || isGAppsInstalled())

        // 恢复安装完启动游戏开关
        mShouldLaunchGameAfterInstallation = true

        connectService {
            val installClosure: () -> Unit = {
                if (shouldLaunchGameAfterInstallation) {
                    runOnUiThread {
                        val gameEntity = toGameEntity(downloadEntity)
                        try {
                            context.startActivity(VSpaceLoadingActivity.getIntent(context, gameEntity, true))
                        } catch (e: Exception) {
                            ToastUtils.toast(e.localizedMessage ?: "启动游戏异常，请稍候再试")
                        }
                    }
                }

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

                        runOnUiThread {
                            val intent = VirtualAppManager.getInstallIntent(context, path, downloadEntity.packageName)

                            Utils.log(LOG_TAG, "正在安装 ${downloadEntity.packageName}")
                            context.startActivity(intent)
                        }
                    } catch (e: Exception) {
                        ToastUtils.toast(e.localizedMessage ?: "")
                    }
                }
            }

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

    /**
     * 批量安装
     * 安装完成后不插入数据库，目前应用于谷歌框架安装
     */
    fun batchInstall(context: Context, installMap: HashMap<String, File>) {
        Utils.log(LOG_TAG, "尝试批量安装 ${installMap.size} 个新应用")

        mBatchInstallMap = installMap
        mBatchInstallFailedCount = 0
        install(context, installMap.values.first())
    }

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

    /**
     * 安装新应用
     * 不会作为游戏存储，目前应用于谷歌框架
     * 因为是静默安装，所以不检查其它限制条件
     */
    private fun install(context: Context, file: File) {
        Utils.log(LOG_TAG, "尝试安装新应用 ${file.name}")

        val packageName = file.nameWithoutExtension

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

                    runOnUiThread {
                        val intent = VirtualAppManager.getInstallIntent(context, path, packageName)

                        Utils.log(LOG_TAG, "正在安装 $packageName")
                        context.startActivity(intent)
                    }
                } catch (e: Exception) {
                    Utils.log(LOG_TAG, "安装异常，${e.localizedMessage}")
                }
            }
        }

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

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

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

    /**
     * 安装完成回调
     */
    fun onInstallFinished(packageName: String, result: VGameInstallerResult) {
        runOnIoThread {
            val gameId = mTempPackageNameAndGameIdMap[packageName] ?: ""

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

            if (downloadEntity != null) {
                // 去掉更新标记
                downloadEntity.isUpdate = false
                mVGameDao.insert(VGameEntity.from(downloadEntity))

                if (result.status == 0) {
                    updateInstalledList()
                    PackageObserver.onPackageChanged(
                        EBPackage(EBPackage.TYPE_INSTALLED, result.packageName, "unknown").also {
                            it.gameId = downloadEntity.gameId
                            it.isVGame = true
                        }
                    )
                    insertInstalledGameToProvider(downloadEntity)
                    runOnUiThread {
                        showFloatingWindow(downloadEntity)
                    }
                } else {
                    ToastUtils.toast("安装出现异常， ${result.status}")
                }

                mInstallingVaPathSet.remove(downloadEntity.path)

                mPackageInstalledLiveData.postValue(result.packageName)

                VBackupHelper.backupDBToExternalStorage(HaloApp.getInstance())
            } else {
                // downloadEntity 为空，可能是框架类的安装 （走另外一个下载管理）
                if (result.status == 0) {
                    updateInstalledList()
                } else {
                    Utils.log(LOG_TAG, "安装出现异常， ${result.status}")
                }
            }

            // 更新已安装的任务，执行其它等待安装的任务
            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()
                        mInstallingVaPathSet.remove(keySet.value.path)

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

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

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

    /**
     * 游戏是否正在安装中
     */
    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 获取）
     * 下载管理不存在时从畅玩游戏数据库的里获取
     */
    private fun getVDownloadEntity(gameId: String, packageName: String): DownloadEntity? {
        return DownloadManager.getInstance().getDownloadEntitySnapshot("", gameId, true)
            ?: getVDownloadEntitySnapshot(gameId = gameId, packageName = packageName)
    }

    private fun insertInstalledGameToProvider(downloadEntity: DownloadEntity, fromRetry: Boolean = false) {
        // 因为 downloadEntity 的 meta 会在下载线程被修改，所以 GsonUtils.toJson(downloadEntity.meta) 会出现ConcurrentModificationException
        // 这里切换线程再做一次尝试
        runOnIoThread(!fromRetry) {
            try {
                val values = ContentValues()
                val packageName = downloadEntity.packageName
                if (packageName.isEmpty()) return@runOnIoThread

                //主键
                values.put("package_name", packageName)
                values.put("url", downloadEntity.url)
                values.put("name", downloadEntity.name)
                values.put("size", downloadEntity.size)
                values.put("meta", GsonUtils.toJson(downloadEntity.meta))
                val type = if (PackageFlavorHelper.IS_TEST_FLAVOR) "test_flavor" else ""
                values.put("type", type)
                val uri = Uri.parse("content://com.lg.core.provider/download_game")
                HaloApp.getInstance().contentResolver.insert(uri, values)
            } catch (e: Throwable) {
                if (!fromRetry) {
                    insertInstalledGameToProvider(downloadEntity, true)
                } else {
                    SentryHelper.onEvent("INSERT_GAME_TO_PROVIDER_ERROR", "packName", downloadEntity.packageName)
                }
            }
        }
    }

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

        installOrLaunch(
            context,
            downloadEntity.packageName,
            downloadEntity.gameId,
            downloadEntity.name,
            downloadEntity.getMetaExtra(KEY_BIT)
        )
    }

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

        installOrLaunch(
            context,
            gameEntity.getUniquePackageName() ?: "",
            gameEntity.id,
            gameEntity.name ?: "",
            gameEntity.getApk()[0].bit
        )
    }

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

        validateVSpaceBeforeAction(context, GameEntity().apply {
            id = gameId
            name = gameName
            downloadStatus = "smooth"
            setApk(arrayListOf(ApkEntity(packageName = packageName, bit = bit)))
        }) {
            if (isInstalled(packageName)) {
                launch(context, packageName)
            } else {
                // 这里重新检查一遍是否已安装，因为有可能因为上一次更新列表失败而出现重新下载的情况
                if (isInstalled(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")

        // 置空下载挂起回调（能 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) && !isGAppsInstalled()) {
            // show dialog
            val currentActivity = CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity ?: return
            GAppsDownloadDialogFragment
                .getInstance(packageName, gameId, gameName)
                .show(currentActivity.supportFragmentManager, "G_APPS_FRAGMENT")
            return
        }

        try {
            if (showLoading) {
                val intent = mDelegateManager.getStartGameIntent(
                    packageName,
                    gameId,
                    gameName,
                    gameIcon,
                    MetaUtil.getBase64EncodedAndroidId(),
                    HaloApp.getInstance().gid,
                    com.gh.gamecenter.BuildConfig.VERSION_NAME,
                    HaloApp.getInstance().channel
                )
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                context.startActivity(intent)
            } else {
                VirtualAppManager.get().launchGame(packageName)
            }

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

            runOnIoThread {
                updateVGamePlayedTime(packageName, System.currentTimeMillis(), getPlayedTime(packageName))
                VBackupHelper.backupDBToExternalStorage(HaloApp.getInstance())

                val isThisVGameExistInDatabase = getAllVGame().any { it.packageName == packageName }

                // 存在安装完成任务，但数据没有正常插入到畅玩数据库的情况(磁盘满了？)，遇到这种情况，手动再插一遍
                if (!isThisVGameExistInDatabase && downloadEntity != null) {
                    mVGameDao.insert(VGameEntity.from(downloadEntity))
                }
            }
        } catch (e: Exception) {
            ToastUtils.toast(e.localizedMessage ?: "")
        }
    }

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

        if (packageName.isNullOrBlank()) return

        mVGameDao.delete(packageName)

        if (mVGameDao.getAll().isEmpty()) {
            VBackupHelper.removeAllDatabase()
        } else {
            VBackupHelper.backupDBToExternalStorage(HaloApp.getInstance())
        }

        val uninstallClosure: () -> Unit = {
            try {
                val result = VirtualAppManager.get().uninstallGame(packageName)
                if (result) {
                    updateInstalledList()
                    // 卸载的时候移除已安装的包名历史
                    mPackageInstalledLiveData.postValue("")
                }

                Utils.log(LOG_TAG, "卸载应用结果 -> $result")
            } catch (e: Exception) {
                ToastUtils.toast(e.localizedMessage ?: "")
            }
        }

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

    /**
     * 获取畅玩助手版本号
     */
    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
        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
            )
        }
    }

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

    /**
     * 获取游戏的游玩时长
     */
    fun getTotalPlayedTime(packageName: String): Long {
        return 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())
            try {
                mVGameDao.insert(it)
            } catch (e: SQLiteFullException) {
                ToastUtils.toast("设备存储空间不足，请清理后重试")
            }
        }

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

    /**
     * 获取畅玩安装完成的 LiveData，内容是包名
     */
    fun getPackageInstalledLiveData(): LiveData<String> = mPackageInstalledLiveData

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

        VLoadCompleteWindowHelper.showFloatingWindow(topActivity, toGameEntity(downloadEntity))
    }

    /**
     * 畅玩空间是否已安装
     * 如果已安装或配置为空返回 true
     * 未安装的情况下会弹弹窗
     */
    fun showDialogIfVSpaceIsNeeded(context: Context, gameId: String, gameName: 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),
                    gameId = gameId,
                    gameName = gameName,
                    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 = getVUrl(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.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.removeUpdate(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 ?: ""

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

        originDownloadEntity.exposureTrace = GsonUtils.toJson(event)

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

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

    /***
     * 实体转换为普通游戏实体
     */
    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
                    )
                )
            )
            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 = "smooth"
            setEntryMap(DownloadManager.getInstance().getEntryMap(name))
        }
    }

    /**
     * 获取畅玩游戏游玩时长
     */
    private fun getPlayedTime(packageName: String, downloadEntity: DownloadEntity? = null): Long {
        var playedTime = mInstalledInfoList.firstOrNull { it.packageName == packageName }?.appTotalPlayTime ?: 0L

        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(validInstalledPackageList, true)
    }

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

        val hasNewerVersion = installedSpaceVersionCode < updateEntity.versionCode

        if (!hasNewerVersion) return false

        if (updateEntity.isAlertEveryTime()) return true

        val lastAlertUpdateUrl = updateEntity.url + updateEntity.alert
        if (updateEntity.isAlertOnceADay()
            && SPUtils.getString(KEY_LAST_ALERT_UPDATE_URL) != lastAlertUpdateUrl
        ) {
            return true
        }

        return false
    }

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

    /**
     * 恢复畅玩数据
     */
    @JvmStatic
    fun recoverVDataIfPossible() {
        VBackupHelper.recoverValidData(HaloApp.getInstance())
    }

    /**
     * 获取畅玩游戏的下载地址（为了区分测试时与普通游戏一样的下载地址）
     */
    fun getVUrl(originUrl: String?): String {
        if (originUrl?.endsWith("type=v") == false) {
            return if (originUrl.contains("?")) {
                "$originUrl&type=v"
            } else {
                "$originUrl?type=v"
            }
        }
        return originUrl ?: ""
    }

    /**
     * 获取未经畅玩处理的原始下载地址
     */
    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()

    /**
     * 畅玩功能是否启用
     */
    @JvmStatic
    fun isVGameOn() = BuildConfig.DEBUG
            || (BuildConfig.IS_VGAME_ON && Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1 && Config.isVGameEnabled())

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

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

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

    /**
     * 是否需要安装 GApps
     */
    fun isGAppsRequired(gameEntity: GameEntity?): Boolean {
        return gameEntity?.gAppsSwitch == "on"
    }

    /**
     * 是否已经成功安装了 GApps
     */
    fun isGAppsInstalled(): Boolean {
        return isInstalled(G_GMS_PACKAGE_NAME)
                && isInstalled(G_GSF_PACKAGE_NAME)
                && isInstalled(G_VENDING_PACKAGE_NAME)
    }

}