402 lines
14 KiB
Kotlin
402 lines
14 KiB
Kotlin
package com.gh.vspace
|
||
|
||
import android.annotation.SuppressLint
|
||
import android.content.Context
|
||
import android.content.Intent
|
||
import android.database.sqlite.SQLiteDiskIOException
|
||
import android.os.*
|
||
import androidx.annotation.WorkerThread
|
||
import com.blankj.utilcode.util.LogUtils
|
||
import com.gh.download.simple.DownloadMessageHandler
|
||
import com.gh.download.simple.SimpleDownloadManager
|
||
import com.gh.gamecenter.common.retrofit.BiResponse
|
||
import com.gh.gamecenter.common.utils.tryWithDefaultCatch
|
||
import com.gh.gamecenter.core.AppExecutor
|
||
import com.gh.gamecenter.core.runOnIoThread
|
||
import com.gh.gamecenter.core.runOnUiThread
|
||
import com.gh.gamecenter.core.utils.MD5Utils
|
||
import com.gh.gamecenter.core.utils.ToastUtils
|
||
import com.gh.gamecenter.entity.ArchiveEntity
|
||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||
import com.gh.vspace.db.VArchiveEntity
|
||
import com.gh.vspace.db.VGameDatabase
|
||
import com.halo.assistant.HaloApp
|
||
import com.lg.download.DownloadStatus
|
||
import com.lg.download.httpclient.DefaultHttpClient
|
||
import com.lg.ndownload.DownloadConfigBuilder
|
||
import com.lg.vspace.VirtualAppManager
|
||
import com.lg.vspace.archive.data.Const
|
||
import com.lg.vspace.archive.ui.ArchiveManager
|
||
import com.lg.vspace.archive.ui.interfaces.ArchiveProgressPageListener
|
||
import com.lightgame.utils.Utils
|
||
import com.lody.virtual.client.core.VirtualCore
|
||
import io.reactivex.schedulers.Schedulers
|
||
import okhttp3.ResponseBody
|
||
import java.io.File
|
||
|
||
object VArchiveHelper {
|
||
|
||
val vArchiveDao by lazy { VGameDatabase.instance.vArchiveDao() }
|
||
|
||
private var mLatestArchiveFile: File? = null // 最近一个用于保存的存档文件
|
||
private var mSaveArchiveListener: ((VArchiveEntity) -> Unit)? = null
|
||
private var mApplyArchiveListener: ((String, Boolean) -> Unit)? = null
|
||
|
||
private var mVArchiveEntityList: ArrayList<VArchiveEntity> = arrayListOf()
|
||
private var mVArchiveMd5Set: HashSet<String> = hashSetOf()
|
||
|
||
private var mLatestDownloadingArchiveEntity: ArchiveEntity? = null // 最近一个正在下载的存档实例
|
||
|
||
// 本地存档存放的位置
|
||
val archivePath by lazy { HaloApp.getInstance().filesDir.absolutePath + File.separator }
|
||
|
||
private var messenger: Messenger? = null
|
||
private val handler = object : Handler(Looper.getMainLooper()) {
|
||
override fun handleMessage(msg: Message) {
|
||
when (msg.what) {
|
||
Const.MSG_USE_ARCHIVE -> {
|
||
msg.data?.let {
|
||
onApplyGameArchiveFinished(
|
||
packageName = it.getString(Const.EXTRA_PACKAGE_NAME, ""),
|
||
isSuccess = it.getBoolean(Const.EXTRA_IS_SUCCESS, false)
|
||
)
|
||
}
|
||
}
|
||
Const.MSG_GENERATE_ARCHIVE -> {
|
||
msg.data?.let {
|
||
onSaveGameArchiveFinished(
|
||
packageName = it.getString(Const.EXTRA_PACKAGE_NAME, ""),
|
||
isSuccess = it.getBoolean(Const.EXTRA_IS_SUCCESS, false)
|
||
)
|
||
}
|
||
}
|
||
}
|
||
messenger = null
|
||
}
|
||
}
|
||
|
||
fun init() {
|
||
DownloadMessageHandler.registerGlobalStatusChangedListener { simpleDownloadEntity, downloadStatus ->
|
||
when (downloadStatus) {
|
||
DownloadStatus.COMPLETED -> {
|
||
mLatestDownloadingArchiveEntity?.let {
|
||
Utils.log(VHelper.LOG_TAG, "新的存档 ${mLatestDownloadingArchiveEntity?.id} 下载完成")
|
||
if (it.id == simpleDownloadEntity.id) {
|
||
val vArchiveEntity = VArchiveEntity(
|
||
id = it.id,
|
||
gameId = it.gameId,
|
||
descContent = it.desc,
|
||
name = it.name,
|
||
md5 = it.md5,
|
||
type = 1,
|
||
filePath = simpleDownloadEntity.dirPath + simpleDownloadEntity.fileName,
|
||
gameVersion = it.gameVersion,
|
||
time = it.time.update
|
||
)
|
||
|
||
Utils.log(VHelper.LOG_TAG, "新的存档 ${it.id} 保存至数据库中")
|
||
runOnIoThread {
|
||
vArchiveDao.insert(vArchiveEntity)
|
||
refreshArchiveSnapshot()
|
||
}
|
||
|
||
syncDownloadArchive(vArchiveEntity)
|
||
}
|
||
mLatestDownloadingArchiveEntity = null
|
||
}
|
||
}
|
||
else -> {
|
||
// do nothing
|
||
}
|
||
}
|
||
}
|
||
|
||
runOnIoThread {
|
||
refreshArchiveSnapshot()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 手动更新存档相关的内存快照
|
||
*/
|
||
@WorkerThread
|
||
fun refreshArchiveSnapshot() {
|
||
try {
|
||
mVArchiveEntityList = ArrayList(vArchiveDao.getAll())
|
||
} catch (e: SQLiteDiskIOException) {
|
||
ToastUtils.toast("磁盘出现异常,请稍后再试")
|
||
e.printStackTrace()
|
||
return
|
||
}
|
||
|
||
// 更新已拥有的存档数据
|
||
val archiveMd5Set = hashSetOf<String>()
|
||
for (vArchiveEntity in mVArchiveEntityList) {
|
||
archiveMd5Set.add(vArchiveEntity.md5)
|
||
}
|
||
mVArchiveMd5Set = archiveMd5Set
|
||
}
|
||
|
||
fun getVArchiveEntityList(): ArrayList<VArchiveEntity> {
|
||
return mVArchiveEntityList
|
||
}
|
||
|
||
@SuppressLint("CheckResult")
|
||
private fun syncDownloadArchive(archiveEntity: VArchiveEntity) {
|
||
RetrofitManager.getInstance().newApi.syncGameArchive(archiveEntity.gameId, archiveEntity.id)
|
||
.subscribeOn(Schedulers.io())
|
||
.subscribe(object : BiResponse<ResponseBody>() {
|
||
override fun onSuccess(data: ResponseBody) {
|
||
archiveEntity.isLocal = VArchiveEntity.UPLOADED
|
||
vArchiveDao.update(archiveEntity)
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 下载新的存档
|
||
*/
|
||
@SuppressLint("CheckResult")
|
||
fun downloadArchive(archive: ArchiveEntity) {
|
||
Utils.log(VHelper.LOG_TAG, "下载新的存档 ${archive.id}")
|
||
|
||
mLatestDownloadingArchiveEntity = archive
|
||
|
||
// 因为没有下载管理,所以每次下载前都需要清理旧的下载,避免无法下载
|
||
SimpleDownloadManager.cancel(archive.id)
|
||
SimpleDownloadManager.download(
|
||
DownloadConfigBuilder()
|
||
.setUniqueId(archive.id)
|
||
.setFileName(archive.id + ".zip")
|
||
.setUrl(archive.url)
|
||
.setPathToStore(archivePath)
|
||
.setHttpClient(DefaultHttpClient())
|
||
.setDownloadThreadSize(2)
|
||
.setDownloadListener(DownloadMessageHandler)
|
||
.setDownloadExecutor(AppExecutor.downloadExecutor)
|
||
.build()
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 创建游戏存档
|
||
* @param packageName 游戏包名
|
||
* @param config 该游戏对应的配置 (不是 url,是 config 的内容)
|
||
* @param archiveFile 游戏存档需要保存的 file (用 MD5 哈希当前时间戳来当文件名)
|
||
*/
|
||
fun saveGameArchive(
|
||
context: Context,
|
||
packageName: String,
|
||
config: String,
|
||
archiveFile: File,
|
||
saveArchiveListener: ((VArchiveEntity) -> Unit)? = null
|
||
) {
|
||
mSaveArchiveListener = saveArchiveListener
|
||
// 记录最近一次创建的存档文件
|
||
mLatestArchiveFile = archiveFile
|
||
if (VHelper.isLegacyInstalled(packageName)) {
|
||
val intent = VirtualAppManager.getGenerateArchiveIntent(context, config, packageName, archiveFile)
|
||
context.startActivity(intent)
|
||
} else {
|
||
if (VirtualCore.get().isRunInExtProcess(packageName)) {
|
||
val intent = VirtualAppManager.getGenerateArchiveIntent(context, config, packageName, archiveFile)
|
||
prepareExtIntent(intent)
|
||
context.startActivity(intent)
|
||
} else {
|
||
ArchiveManager.generatePackage(context, packageName, config, archiveFile,
|
||
object : ArchiveProgressPageListener {
|
||
override fun onCancel() {
|
||
}
|
||
|
||
override fun completed(isSuccess: Boolean) {
|
||
onSaveGameArchiveFinished(packageName, isSuccess)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用游戏存档
|
||
* @param packageName 游戏包名
|
||
* @param archiveFile 游戏存档的 file
|
||
*/
|
||
fun applyGameArchive(
|
||
context: Context,
|
||
packageName: String,
|
||
config: String,
|
||
archiveFile: File,
|
||
applyArchiveListener: ((String, Boolean) -> Unit)? = null
|
||
) {
|
||
mApplyArchiveListener = applyArchiveListener
|
||
if (VHelper.isLegacyInstalled(packageName)) {
|
||
val intent = VirtualAppManager.getUseArchiveIntent(context, packageName, config, archiveFile)
|
||
context.startActivity(intent)
|
||
} else {
|
||
if (VirtualCore.get().isRunInExtProcess(packageName)) {
|
||
val intent = VirtualAppManager.getUseArchiveIntent(context, packageName, config, archiveFile)
|
||
prepareExtIntent(intent)
|
||
context.startActivity(intent)
|
||
} else {
|
||
ArchiveManager.useArchive(context, packageName, archiveFile, config,
|
||
object : ArchiveProgressPageListener {
|
||
override fun onCancel() {
|
||
}
|
||
|
||
override fun completed(isSuccess: Boolean) {
|
||
LogUtils.d("存档使用 completed : $isSuccess")
|
||
if (applyArchiveListener != null) {
|
||
applyArchiveListener(packageName, isSuccess)
|
||
}
|
||
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun prepareExtIntent(intent: Intent) {
|
||
intent.setClassName(
|
||
com.lg.vspace.BuildConfig.EXT_PACKAGE_NAME,
|
||
"com.lg.vspace.addon.launcher.RemoteGuideActivity"
|
||
)
|
||
intent.putExtra("callback", Bundle().also {
|
||
messenger = Messenger(handler)
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||
it.putBinder("messenger", messenger!!.binder)
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 创建游戏存档结束回调
|
||
* @param packageName 游戏包名
|
||
* @param isSuccess 保存存档是否成功
|
||
*/
|
||
fun onSaveGameArchiveFinished(packageName: String, isSuccess: Boolean) {
|
||
if (isSuccess) {
|
||
val vGameEntity = VHelper.getVGameSnapshot(packageName = packageName)
|
||
runOnIoThread {
|
||
val fileMd5 = MD5Utils.calculateMD5(mLatestArchiveFile) ?: return@runOnIoThread
|
||
val vArchiveEntity = VArchiveEntity(
|
||
id = fileMd5,
|
||
gameId = vGameEntity?.downloadEntity?.gameId ?: "",
|
||
md5 = fileMd5,
|
||
filePath = mLatestArchiveFile?.absolutePath ?: "",
|
||
time = System.currentTimeMillis(),
|
||
type = 0,
|
||
gameVersion = vGameEntity?.downloadEntity?.versionName ?: "",
|
||
)
|
||
|
||
vArchiveDao.insert(vArchiveEntity)
|
||
refreshArchiveSnapshot()
|
||
|
||
runOnUiThread {
|
||
mSaveArchiveListener?.invoke(vArchiveEntity)
|
||
}
|
||
|
||
mLatestArchiveFile = null
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用游戏存档结束回调
|
||
* @param packageName 游戏包名
|
||
* @param isSuccess 应用存档是否成功
|
||
*/
|
||
fun onApplyGameArchiveFinished(packageName: String, isSuccess: Boolean) {
|
||
mApplyArchiveListener?.invoke(packageName, isSuccess)
|
||
}
|
||
|
||
/**
|
||
* 本地是否已经拥有该 md5 对应的存档
|
||
*/
|
||
fun isArchiveDownloaded(md5: String): Boolean = mVArchiveMd5Set.contains(md5)
|
||
|
||
/**
|
||
* 根据 md5 获取对应的存档文件 (file)
|
||
*/
|
||
fun getArchiveFile(md5: String): File? {
|
||
for (vArchiveEntity in mVArchiveEntityList) {
|
||
if (md5 == vArchiveEntity.md5) {
|
||
return File(vArchiveEntity.filePath)
|
||
}
|
||
}
|
||
return null
|
||
}
|
||
|
||
/**
|
||
* 根据 md5 获取对应的存档文件路径
|
||
*/
|
||
fun getArchiveFilePath(md5: String): String {
|
||
for (vArchiveEntity in mVArchiveEntityList) {
|
||
if (md5 == vArchiveEntity.md5) {
|
||
return vArchiveEntity.filePath
|
||
}
|
||
}
|
||
return "";
|
||
}
|
||
|
||
/**
|
||
* 根据 md5 删除对应的存档文件
|
||
*/
|
||
fun deleteArchive(md5: String) {
|
||
for (vArchiveEntity in mVArchiveEntityList) {
|
||
if (md5 == vArchiveEntity.md5) {
|
||
runOnIoThread {
|
||
vArchiveDao.delete(vArchiveEntity)
|
||
refreshArchiveSnapshot()
|
||
tryWithDefaultCatch {
|
||
File(vArchiveEntity.filePath).delete()
|
||
}
|
||
}
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清空存档文件
|
||
*/
|
||
@WorkerThread
|
||
fun clearAllArchives() {
|
||
vArchiveDao.clearAllArchives()
|
||
}
|
||
|
||
/**
|
||
* 批量更新存档文件
|
||
*/
|
||
@WorkerThread
|
||
fun updateArchives(archiveEntityList: ArrayList<VArchiveEntity>) {
|
||
vArchiveDao.update(archiveEntityList)
|
||
}
|
||
|
||
/**
|
||
* 设置创建游戏存档成功结果监听
|
||
*/
|
||
fun setSaveArchiveListener(listener: (VArchiveEntity) -> Unit) {
|
||
mSaveArchiveListener = listener
|
||
}
|
||
|
||
/**
|
||
* 移除创建游戏存档成功结果监听
|
||
*/
|
||
fun clearSaveArchiveListener() {
|
||
mSaveArchiveListener = null
|
||
}
|
||
|
||
/**
|
||
* 设置使用游戏存档成功结果监听
|
||
*/
|
||
fun setApplyArchiveListener(listener: (String, Boolean) -> Unit) {
|
||
mApplyArchiveListener = listener
|
||
}
|
||
|
||
/**
|
||
* 移除使用游戏存档成功结果监听
|
||
*/
|
||
fun clearApplyArchiveListener() {
|
||
mApplyArchiveListener = null
|
||
}
|
||
} |