2093 lines
80 KiB
Kotlin
2093 lines
80 KiB
Kotlin
package com.gh.vspace
|
||
|
||
import android.annotation.SuppressLint
|
||
import android.content.ComponentName
|
||
import android.content.ContentValues
|
||
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 androidx.annotation.WorkerThread
|
||
import androidx.appcompat.app.AppCompatActivity
|
||
import androidx.lifecycle.*
|
||
import com.g00fy2.versioncompare.Version
|
||
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.R
|
||
import com.gh.gamecenter.SplashScreenActivity
|
||
import com.gh.gamecenter.common.base.GlobalActivityManager
|
||
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.feature.entity.InstallGameEntity
|
||
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.download.DownloadError
|
||
import com.lg.download.DownloadStatus
|
||
import com.lg.download.httpclient.DefaultHttpClient
|
||
import com.lg.ndownload.DownloadConfigBuilder
|
||
import com.lg.vspace.VaApp
|
||
import com.lg.vspace.VirtualAppManager
|
||
import com.lg.vspace.bridge.BuildConfig
|
||
import com.lg.vspace.plugin.host.PluginFileUtils
|
||
import com.lg.vspace.plugin.host.PluginHelper
|
||
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.VGameInstallerParams
|
||
import com.lg.vspace.remote.model.VGameInstallerResult
|
||
import com.lightgame.download.DownloadEntity
|
||
import com.lightgame.utils.AppManager
|
||
import com.lightgame.utils.Utils
|
||
import com.va.host.HostUtils
|
||
import io.reactivex.Completable
|
||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||
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.concurrent.TimeUnit
|
||
|
||
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 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 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 VSPACE_32BIT_NEW_PACKAGENAME = com.lg.vspace.BuildConfig.EXT_PACKAGE_NAME
|
||
|
||
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
|
||
|
||
val vGameDao 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 mMustInitialized = 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 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
|
||
|
||
// 临时的用来临时匹配安装完成时包名对应的游戏 ID 的 Map
|
||
private var mTempPackageNameAndGameIdMap = hashMapOf<String, String>()
|
||
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()
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化
|
||
*/
|
||
@SuppressLint("CheckResult")
|
||
@JvmStatic
|
||
fun init(context: Context) {
|
||
Completable.fromAction {
|
||
refreshVGameSnapshot()
|
||
}.subscribeOn(Schedulers.io())
|
||
.observeOn(AndroidSchedulers.mainThread())
|
||
.subscribe {
|
||
onInit(context)
|
||
}
|
||
}
|
||
|
||
private fun onInit(context: Context) {
|
||
if (!mMustInitialized) {
|
||
mMustInitialized = true
|
||
VArchiveHelper.init()
|
||
val allGames = mVGameSnapshotList.map { it.packageName }.toArrayList()
|
||
if (allGames.isNotEmpty()) {
|
||
PackageRepository.addInstalledGames(allGames, true)
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
})
|
||
}
|
||
checkVspaceUpdate(context)
|
||
}
|
||
}
|
||
|
||
|
||
private fun checkVspaceUpdate(context: Context) {
|
||
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
|
||
|
||
// 断开时清空正在安装列表
|
||
mInstallingVaPathSet.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 {
|
||
gameIcon = it.downloadEntity.icon
|
||
gameName = it.downloadEntity.getMetaExtra(Constants.GAME_NAME)
|
||
gameVersion = it.downloadEntity.versionName
|
||
packageName = it.packageName
|
||
}
|
||
}.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()
|
||
}
|
||
|
||
/**
|
||
* 此语义是判断是否是安装的畅玩游戏
|
||
* 畅玩组件是否已安装此包名的畅玩游戏, 包含 1. 分体式畅玩, 2. 一体式畅玩 两个地方
|
||
* 否则应该单独针对分体式和一体式判断
|
||
*/
|
||
@JvmStatic
|
||
fun isInstalled(packageName: String?) = isInnerInstalled(packageName) || isLegacyInstalled(packageName)
|
||
|
||
/**
|
||
* 分体式畅玩是否安装了游戏
|
||
*/
|
||
@JvmStatic
|
||
fun isLegacyInstalled(packageName: String?): Boolean {
|
||
var isInstalled = mInstalledInfoList.any { it.packageName == packageName }
|
||
|
||
Utils.log(LOG_TAG, "$packageName 已安装列表里 -> $isInstalled")
|
||
|
||
// 有可能 mInstalledInfoList 获取异常,导致判断为未安装,手动调用 AIDL 再查一次
|
||
if (!isInstalled
|
||
&& mIsServiceConnected
|
||
&& mDelegateManager.isConnectAidlInterface
|
||
) {
|
||
try {
|
||
isInstalled = mDelegateManager.checkGameInstalled(packageName)
|
||
Utils.log(LOG_TAG, "手动调用 AIDL 获取安装情况 -> $isInstalled")
|
||
} catch (e: RuntimeException) {
|
||
Utils.log(LOG_TAG, "手动调用 AIDL 获取安装情况异常 ${e.localizedMessage}")
|
||
}
|
||
}
|
||
|
||
return isInstalled
|
||
}
|
||
|
||
@JvmStatic
|
||
fun isInnerInstalled(packageName: String?) =
|
||
VaApp.get().appManager.installedGamesInfo.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)) {
|
||
VaApp.get().appManager.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(
|
||
gameEntity?.id ?: "",
|
||
gameEntity?.name ?: "",
|
||
if (gameBit == "32") "32位" else "64位"
|
||
)
|
||
SPUtils.setString(
|
||
if (updateEntity.category == "32-bit") KEY_LAST_ALERT_32_UPDATE_URL else KEY_LAST_ALERT_64_UPDATE_URL,
|
||
updateEntity.url + updateEntity.alert
|
||
)
|
||
DialogHelper.showDialog(
|
||
context = context,
|
||
title = "服务工具更新提示",
|
||
content = updateEntity.content.toString(),
|
||
cancelText = "立即更新",
|
||
confirmText = "继续游戏",
|
||
cancelClickCallback = {
|
||
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
|
||
NewFlatLogUtils.logHaloFunUpdateDialogClick(
|
||
dialogType,
|
||
"立即更新",
|
||
if (appEntity64 != null && appEntity32 != null) "32位&64位" else if (appEntity64 != null) "64位" else "32位"
|
||
)
|
||
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.text_theme.toColor(context))
|
||
}
|
||
|
||
it.confirmTv.setTextColor(R.color.text_secondary.toColor(context))
|
||
}
|
||
)
|
||
return
|
||
}
|
||
|
||
callback.invoke()
|
||
}
|
||
|
||
/**
|
||
* 判断是否需要安装32位组件,64位直接callback
|
||
*/
|
||
private 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(
|
||
gameEntity?.id ?: "",
|
||
gameEntity?.name ?: "",
|
||
"32位"
|
||
)
|
||
DialogHelper.showDialog(
|
||
context = context,
|
||
title = "服务工具更新提示",
|
||
content = new32UpdateEntity.content.toString(),
|
||
cancelText = "立即更新",
|
||
confirmText = if (new32UpdateEntity.isForce) "取消" else "继续游戏",
|
||
cancelClickCallback = {
|
||
NewFlatLogUtils.logHaloFunUpdateDialogClick(
|
||
dialogType,
|
||
"立即更新",
|
||
"32位"
|
||
)
|
||
VSpaceUpdate32NewDialogFragment.showDownloadDialog(
|
||
context,
|
||
new32UpdateEntity,
|
||
gameEntity,
|
||
autoDownload = true,
|
||
isUpdate = true
|
||
)
|
||
},
|
||
confirmClickCallback = {
|
||
NewFlatLogUtils.logHaloFunUpdateDialogClick(dialogType, "继续游戏", "")
|
||
if (!new32UpdateEntity.isForce) {
|
||
callback.invoke()
|
||
}
|
||
},
|
||
extraConfig = DialogHelper.Config(centerTitle = true),
|
||
uiModificationCallback = {
|
||
if (new32UpdateEntity.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()
|
||
}
|
||
|
||
/**
|
||
* 获取已安装的包名列表
|
||
*/
|
||
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
|
||
|
||
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)) {
|
||
try {
|
||
mInstallingVaPathSet.add(path)
|
||
|
||
if (isLegacyGame) {
|
||
runOnUiThread {
|
||
val intent = VirtualAppManager.getInstallIntent(context, path, downloadEntity.packageName)
|
||
|
||
Utils.log(LOG_TAG, "正在安装 ${downloadEntity.packageName}")
|
||
context.startActivity(intent)
|
||
}
|
||
} else {
|
||
runOnIoThread {
|
||
onInstallFinished(
|
||
downloadEntity.packageName,
|
||
VaApp.get().appManager.installGame(
|
||
path,
|
||
VGameInstallerParams(VGameInstallerParams.FLAG_INSTALL_OVERRIDE_NO_CHECK)
|
||
)
|
||
)
|
||
}
|
||
}
|
||
|
||
|
||
} 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)) {
|
||
val intent = VirtualAppManager.getInstallIntent(context, path, packageName)
|
||
|
||
Utils.log(LOG_TAG, "正在安装 $packageName")
|
||
context.startActivity(intent)
|
||
} else {
|
||
runOnIoThread {
|
||
val installParams = VGameInstallerParams(VGameInstallerParams.FLAG_INSTALL_OVERRIDE_NO_CHECK)
|
||
val installGameResult = VaApp.get().appManager.installGame(path, installParams)
|
||
onInstallFinished(packageName, installGameResult)
|
||
}
|
||
}
|
||
} catch (e: Exception) {
|
||
Utils.log(LOG_TAG, "安装异常,${e.localizedMessage}")
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 禁用安装完直接启动游戏 (仅此次安装)
|
||
*/
|
||
fun disableLaunchGameAfterInstallation() {
|
||
mShouldLaunchGameAfterInstallation = false
|
||
}
|
||
|
||
/**
|
||
* 安装完是否直接启动游戏
|
||
*/
|
||
fun shouldLaunchGameAfterInstallation() = mShouldLaunchGameAfterInstallation
|
||
|
||
/**
|
||
* 安装完成回调
|
||
*/
|
||
fun onInstallFinished(packageName: String, result: VGameInstallerResult) {
|
||
if (PackageFlavorHelper.IS_TEST_FLAVOR) {
|
||
callSite.onNext(callSiteOnInstallComplet)
|
||
}
|
||
runOnIoThread {
|
||
val gameId = mTempPackageNameAndGameIdMap[packageName] ?: ""
|
||
|
||
val downloadEntity = getVDownloadEntity(gameId = gameId, packageName = packageName)
|
||
|
||
|
||
if (downloadEntity != null) {
|
||
// 去掉更新标记
|
||
downloadEntity.isUpdate = false
|
||
|
||
mInstallingVaPathSet.remove(downloadEntity.path)
|
||
|
||
try {
|
||
vGameDao.insert(VGameEntity.from(downloadEntity))
|
||
refreshVGameSnapshot()
|
||
} catch (e: SQLiteFullException) {
|
||
ToastUtils.toast("存储空间不足,安装失败")
|
||
return@runOnIoThread
|
||
}
|
||
|
||
if (result.status == 0) {
|
||
if (isLegacyInstalled(packageName)) {
|
||
// 存在旧版畅玩组件的游戏更新时才更新安装列表了,不再会有新的包安装到旧版的畅玩空间了
|
||
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}")
|
||
}
|
||
|
||
mPackageInstalledLiveData.postValue(result.packageName)
|
||
|
||
VBackupHelper.backupDBToExternalStorage(HaloApp.getInstance())
|
||
} else {
|
||
// downloadEntity 为空,可能是框架类的安装 (走另外一个下载管理)
|
||
if (result.status == 0) {
|
||
if (mIsServiceConnected) {
|
||
updateInstalledList()
|
||
}
|
||
} else {
|
||
ToastUtils.toast("安装出现异常, ${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, 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
|
||
}
|
||
|
||
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.vspace.BuildConfig.VA_AUTHORITY_PREFIX}.provider.download_game/download_game")
|
||
HaloApp.getInstance().contentResolver.insert(uri, values)
|
||
} catch (e: Throwable) {
|
||
Utils.log("TEST", "::insertInstalledGameToProvider, e: ${e.toString()}")
|
||
if (!fromRetry) {
|
||
insertInstalledGameToProvider(downloadEntity, true)
|
||
} else {
|
||
SentryHelper.onEvent("INSERT_GAME_TO_PROVIDER_ERROR", "packName", downloadEntity.packageName)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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(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?
|
||
) {
|
||
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 (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")
|
||
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
|
||
)
|
||
// 覆盖字段
|
||
if (!isLegacyGame) {
|
||
intent.setComponent(
|
||
ComponentName(
|
||
com.gh.gamecenter.BuildConfig.APPLICATION_ID,
|
||
VirtualAppManager.AIDL_SERVER_REMOTE_GUIDE_ACTIVITY
|
||
)
|
||
)
|
||
}
|
||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||
context.startActivity(intent)
|
||
} else {
|
||
if (isLegacyGame) {
|
||
VirtualAppManager.get().launchGame(packageName)
|
||
} else {
|
||
VaApp.get().appManager.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) {
|
||
vGameDao.insert(VGameEntity.from(downloadEntity))
|
||
refreshVGameSnapshot()
|
||
}
|
||
}
|
||
} catch (e: Exception) {
|
||
ToastUtils.toast(e.localizedMessage ?: "")
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 记录启动事件
|
||
*/
|
||
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
|
||
|
||
try {
|
||
vGameDao.delete(packageName)
|
||
refreshVGameSnapshot()
|
||
|
||
if (vGameDao.getAll().isEmpty()) {
|
||
VBackupHelper.removeAllDatabase()
|
||
} else {
|
||
VBackupHelper.backupDBToExternalStorage(HaloApp.getInstance())
|
||
}
|
||
} catch (e: SQLiteFullException) {
|
||
e.printStackTrace()
|
||
}
|
||
val isLegacyGame = isLegacyInstalled(packageName)
|
||
val uninstallClosure: () -> Unit = {
|
||
try {
|
||
|
||
val result = if (isLegacyGame) VirtualAppManager.get()
|
||
.uninstallGame(packageName) else VaApp.get().appManager.uninstallGame(packageName)
|
||
if (result) {
|
||
if (isLegacyGame) {
|
||
updateInstalledList()
|
||
}
|
||
// 卸载的时候移除已安装的包名历史
|
||
mPackageInstalledLiveData.postValue("")
|
||
}
|
||
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
|
||
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 {
|
||
vGameDao.insert(it)
|
||
refreshVGameSnapshot()
|
||
} 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,
|
||
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.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 ?: ""
|
||
|
||
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_schema_type", if (originDownloadEntity.getMetaExtra(KEY_BIT) == "32") "32位" else "64位"
|
||
)
|
||
|
||
SensorsBridge.trackEventWithExposureSource(
|
||
"DownloadProcessBegin",
|
||
event.source,
|
||
"game_id", originDownloadEntity.gameId,
|
||
"game_name", originDownloadEntity.name ?: "",
|
||
"game_type", originDownloadEntity.categoryChinese,
|
||
"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,
|
||
"download_type", "畅玩下载",
|
||
)
|
||
}
|
||
|
||
/***
|
||
* 实体转换为普通游戏实体
|
||
*/
|
||
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 = 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,
|
||
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) {
|
||
val entity =
|
||
SubjectRecommendEntity(link = "62fc8047b07e0c0bb63058c2", nameNormal = "畅玩广场", text = "畅玩广场")
|
||
DirectUtils.directToBlock(context, entity, "")
|
||
}
|
||
|
||
/**
|
||
* 恢复畅玩数据
|
||
*/
|
||
@JvmStatic
|
||
fun recoverVDataIfPossible() {
|
||
VBackupHelper.recoverValidData(HaloApp.getInstance())
|
||
}
|
||
|
||
/**
|
||
* 获取未经畅玩处理的原始下载地址
|
||
*/
|
||
@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() = PackageFlavorHelper.IS_TEST_FLAVOR
|
||
|| HaloApp.getInstance().channel == "GH_jzcs" // 兼职包特殊渠道强行开启畅玩服务
|
||
|| (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1 && isVGameOnByConfigSettings())
|
||
|
||
/**
|
||
* 后台接口是否启用畅玩功能(若获取不到开关情况时,判断用户是否曾用过畅玩功能)
|
||
*/
|
||
private fun isVGameOnByConfigSettings(): Boolean {
|
||
val configSettings = Config.getSettings()
|
||
|
||
// 当 settings 为空时,若曾经用过畅玩,也允许用户继续使用畅玩
|
||
return if (configSettings == null) {
|
||
isVIsUsed()
|
||
} else {
|
||
"on" == configSettings.gameSmooth
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查畅玩游戏是否满足生成存档要求
|
||
*/
|
||
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
|
||
*/
|
||
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)
|
||
}
|
||
|
||
@SuppressLint("CheckResult")
|
||
fun preparePluginUpdate() {
|
||
Config.vNewSettingSubject.debounce(2, TimeUnit.SECONDS).doOnNext {
|
||
it?.vaPlugin?.let { vaPlugin ->
|
||
if (!vaPlugin.id.isNullOrEmpty()) {
|
||
Utils.log(LOG_TAG, "开发者需要插件更新功能,请push文件到手机,或者【修改插件打包的配置为debug版本,然后上传,推送】")
|
||
if (!vaPlugin.url64.isNullOrEmpty()) {
|
||
val installedPluginVersion = HostUtils.getPluginVersion()
|
||
// 因为服务端下发的插件都是release版本的,所以,开发者要调试插件更新,暂时先push文件到手机更新吧
|
||
if (true) {
|
||
val file = File("/data/local/tmp/gh-plugins/artifacts.zip")
|
||
if (file.exists()) {
|
||
Utils.log(LOG_TAG, "有本地更新文件: 64位插件")
|
||
PluginHelper.getInstance().updatePlugin(file)
|
||
}
|
||
} else {
|
||
if (installedPluginVersion?.isNotEmpty() == true
|
||
&& !Version(vaPlugin.versionName).isEqual(installedPluginVersion)
|
||
) {
|
||
downloadPlugin(id = "${vaPlugin.id}64", url = vaPlugin.url64) {
|
||
PluginHelper.getInstance().updatePlugin(it)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!vaPlugin.url32.isNullOrEmpty()) {
|
||
if (com.gh.gamecenter.BuildConfig.DEBUG) {
|
||
val file = File("/data/local/tmp/gh-plugins/artifacts32.zip")
|
||
if (file.exists()) {
|
||
Utils.log(LOG_TAG, "有本地更新文件: 32位插件")
|
||
VaApp.get().appManager.updatePlugin(file.absolutePath, "${vaPlugin.id}32.zip")
|
||
}
|
||
} else {
|
||
val installedPluginVersion = VaApp.get().appManager.extPluginVersion
|
||
if (installedPluginVersion?.isNotEmpty() == true
|
||
&& !Version(vaPlugin.versionName).isEqual(installedPluginVersion)
|
||
) {
|
||
downloadPlugin(id = "${vaPlugin.id}32", url = vaPlugin.url32) {
|
||
VaApp.get().appManager.updatePlugin(it.absolutePath, "${vaPlugin.id}32.zip")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}.subscribeOn(Schedulers.io()).subscribe({}, {})
|
||
}
|
||
|
||
private fun downloadPlugin(id: String, url: String, fn: (File) -> Unit) {
|
||
val pluginFileName = "${id}.zip"
|
||
val currentPluginFile = File(HaloApp.getInstance().cacheDir, pluginFileName)
|
||
if (currentPluginFile.exists() && PluginFileUtils.isZipFile(currentPluginFile)) {
|
||
// 已经下载好了
|
||
fn(currentPluginFile)
|
||
} else {
|
||
val currentDownloadEntity = DownloadMessageHandler.findEntity(id)
|
||
if (currentDownloadEntity != null) {
|
||
SimpleDownloadManager.cancel(id)
|
||
}
|
||
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()
|
||
)
|
||
DownloadMessageHandler.registerListener(id, object : DownloadListener {
|
||
override fun onError(error: DownloadError) {
|
||
}
|
||
|
||
override fun onProgress(progress: Float) {
|
||
}
|
||
|
||
override fun onSizeReceived(fileSize: Long) {
|
||
}
|
||
|
||
override fun onStatusChanged(status: DownloadStatus) {
|
||
if (status == DownloadStatus.COMPLETED) {
|
||
fn(currentPluginFile)
|
||
DownloadMessageHandler.unregisterListener(id, this)
|
||
}
|
||
}
|
||
|
||
override fun onSpeedChanged(speed: Float) {
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
} |