package com.gh.common.util import android.preference.PreferenceManager import com.gh.common.constant.Config import com.gh.common.exposure.ExposureUtils import com.gh.common.simulator.SimulatorDownloadManager import com.gh.common.simulator.SimulatorGameManager import com.gh.common.xapk.XapkInstaller import com.gh.download.DownloadDataHelper import com.gh.download.DownloadManager import com.gh.gamecenter.R import com.gh.gamecenter.SuggestionActivity import com.gh.gamecenter.common.base.activity.BaseActivity import com.gh.gamecenter.common.callback.ConfirmListener import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.eventbus.EBShowDialog import com.gh.gamecenter.common.retrofit.Response import com.gh.gamecenter.common.utils.* import com.gh.gamecenter.core.utils.* import com.gh.gamecenter.energy.EnergyBridge import com.gh.gamecenter.entity.GameEntity import com.gh.gamecenter.entity.SimpleGameEntity import com.gh.gamecenter.entity.SimulatorEntity import com.gh.gamecenter.eventbus.EBDownloadStatus import com.gh.gamecenter.retrofit.RetrofitManager import com.gh.gamecenter.setting.view.GameDownloadSettingFragment import com.gh.gamecenter.suggest.SuggestType import com.gh.vspace.VHelper import com.halo.assistant.HaloApp import com.lightgame.download.DataWatcher import com.lightgame.download.DownloadEntity import com.lightgame.download.DownloadStatus import com.lightgame.download.FileUtils import com.lightgame.utils.AppManager import com.lightgame.utils.Utils import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import okhttp3.MediaType import okhttp3.RequestBody import org.greenrobot.eventbus.EventBus import org.json.JSONObject object DownloadObserver { private val mApplication = HaloApp.getInstance().application // 简单 debounce 因为内存更新 downloadEntity 对象造成的触发双重下载完成事件 // TODO 修复因为更改内存对象造成的双重下载完成事件问题,具体触发代码见 DownloadDao.updateSnapshotList private var mDoneDebouncePair: Pair? = null private const val TEA_EVENT_DOWNLOAD_COMPLETE = "game_addiction" // 如果在WIFI状态下,下载自动暂停,则再重试一遍 @JvmStatic fun initObserver() { val dataWatcher = object : DataWatcher() { override fun onDataChanged(downloadEntity: DownloadEntity) { // todo 如何处理xapk安装问题 val xapkStatus = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS] if (!xapkStatus.isNullOrEmpty()) return val gameId = downloadEntity.gameId val downloadManager = DownloadManager.getInstance() tryCatchInRelease { DownloadDataHelper.uploadDownloadEvent(downloadEntity) } if (DownloadStatus.hijack == downloadEntity.status) { // 链接被劫持 processHijack(downloadEntity) return } else if (DownloadStatus.notfound == downloadEntity.status) { // 404 Not Found // 删除任务 downloadEntity.status = DownloadStatus.cancel downloadManager.cancel(downloadEntity.url) Utils.toast(mApplication, "该链接已失效!请联系管理员。") val currentActivity = AppManager.getInstance().currentActivity() ?: return DialogHelper.showDialog(currentActivity, "下载失败", "下载链接已失效,建议提交反馈", "立即反馈", "取消", { SuggestionActivity.startSuggestionActivity( currentActivity, SuggestType.gameQuestion, "notfound", StringUtils.buildString(downloadEntity.name, ",问题反馈:下载链接失效"), SimpleGameEntity(gameId, downloadEntity.name, "") ) }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) return } else if (DownloadStatus.neterror == downloadEntity.status || DownloadStatus.timeout == downloadEntity.status) { if (downloadEntity.meta[Constants.MARK_RETRY_DOWNLOAD].isNullOrEmpty() && NetworkUtils.isWifiConnected(HaloApp.getInstance().application) ) { downloadEntity.meta[Constants.MARK_RETRY_DOWNLOAD] = downloadEntity.progress.toString() downloadManager.updateDownloadEntity(downloadEntity) downloadManager.resumeDownload(downloadEntity.url) debugOnly { Utils.log("DownloadObserver", "下载重试->" + downloadEntity.toJson()) } } else { Utils.toast(mApplication, "网络不稳定,下载任务已暂停") DataLogUtils.uploadNeterrorLog(mApplication, downloadEntity) debugOnly { Utils.log("DownloadObserver", "下载自动暂停->" + downloadEntity.toJson()) } } } else if (DownloadStatus.redirected == downloadEntity.status) { debugOnly { Utils.log("重定向完毕") } DownloadDataHelper.uploadRedirectEvent(downloadEntity) } else if (DownloadStatus.unqualified == downloadEntity.status) { // 未成年 RealNameHelper.showRealNameUnqualifiedDialog(downloadEntity) // 删除任务 downloadEntity.status = DownloadStatus.cancel downloadManager.cancel(downloadEntity.url) } else if (DownloadStatus.unavailable == downloadEntity.status) { // 未接入防沉迷系统 val currentActivity = AppManager.getInstance().currentActivity() if (currentActivity != null) { DialogHelper.showDialog( context = currentActivity, title = "温馨提示", content = "该游戏未接入防沉迷系统,暂不支持下载", confirmText = "知道了", cancelText = "" ) } else { ToastUtils.toast("该游戏未接入防沉迷系统,暂不支持下载") } // 删除任务 downloadEntity.status = DownloadStatus.cancel downloadManager.cancel(downloadEntity.url) } else if (DownloadStatus.uncertificated == downloadEntity.status) { // 未实名 RealNameHelper.showRealNameUncertificatedDialog(downloadEntity) // 删除任务 downloadEntity.status = DownloadStatus.cancel downloadManager.cancel(downloadEntity.url) } else if (DownloadStatus.banned == downloadEntity.status) { ToastUtils.showToast("网络异常") // 删除任务 downloadEntity.status = DownloadStatus.cancel downloadManager.cancel(downloadEntity.url) } if (DownloadStatus.done == downloadEntity.status) { if (mDoneDebouncePair?.first != downloadEntity.url) { mDoneDebouncePair = Pair(downloadEntity.url, System.currentTimeMillis()) performDownloadCompleteAction(downloadEntity, gameId, downloadManager) } else { if (mDoneDebouncePair?.second == 0L || System.currentTimeMillis() - (mDoneDebouncePair?.second ?: 0) > 500 ) { performDownloadCompleteAction(downloadEntity, gameId, downloadManager) } } } if (downloadEntity.status == DownloadStatus.done) { EventBus.getDefault().post(EBDownloadStatus("done", "", "", "", downloadEntity.packageName, "")) } DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity) // 如果已下载大小发生变化,表示成功恢复下载,则重置重试标记 if (downloadEntity.status == DownloadStatus.downloading && downloadEntity.progress.toString() != downloadEntity.meta[Constants.MARK_RETRY_DOWNLOAD] ) { downloadEntity.meta[Constants.MARK_RETRY_DOWNLOAD] = "" downloadManager.updateDownloadEntity(downloadEntity) } } } // 添加观察者 DownloadManager.getInstance().addObserver(dataWatcher) } private fun performDownloadCompleteAction( downloadEntity: DownloadEntity, gameId: String, downloadManager: DownloadManager ) { if (downloadEntity.name.contains(mApplication.getString(R.string.app_name))) { statDoneEvent(downloadEntity) MtaHelper.onEvent("软件更新", "下载完成") // 会有 ActivityNotFoundException 异常,catch 掉不管了 tryWithDefaultCatch { if (Constants.SILENT_UPDATE != downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)) { // TODO 在 Android 11 上没有授权安装未知应用的权限前第一次调用这个方法系统会杀掉我们的进程... // 没能找到类似的解释,最接近的是这个 https://issuetracker.google.com/issues/154157387,但也只是点授权杀进程而已 PackageInstaller.install(mApplication, downloadEntity); DataLogUtils.uploadUpgradeLog(mApplication, "install") //上传更新安装数据 } } } else { statDoneEvent(downloadEntity) if (!SPUtils.getBoolean(TEA_EVENT_DOWNLOAD_COMPLETE)) { HaloApp.getInstance().flavorProvider.logEvent(TEA_EVENT_DOWNLOAD_COMPLETE) SPUtils.setBoolean(TEA_EVENT_DOWNLOAD_COMPLETE, true) } GameActivityDownloadHelper.clear() EnergyBridge.postEnergyTask("download_game", downloadEntity.gameId, downloadEntity.packageName) val platform = PlatformUtils.getInstance(mApplication) .getPlatformName(downloadEntity.platform) if (platform != null) { when { // TODO 插件化传 path 从 apk 中获取 packageName 的形式有可能出现拿不到 packageName 的情况 downloadEntity.isPluggable -> // 弹出插件化提示框 EventBus.getDefault().post( EBShowDialog( BaseActivity.PLUGGABLE, downloadEntity.path ) ) downloadEntity.isPlugin -> Utils.toast( mApplication, downloadEntity.name + " - " + platform + " - 下载完成" ) else -> { if (downloadEntity.isVGame()) { VHelper.showFloatingWindow(downloadEntity.packageName) } else { Utils.toast(mApplication, downloadEntity.name + " - 下载完成") } } } } else { if (downloadEntity.isVGame()) { VHelper.showFloatingWindow(downloadEntity.packageName) } else { Utils.toast(mApplication, downloadEntity.name + " - 下载完成") } } if (!downloadEntity.isPluggable) { if (downloadEntity.isSimulatorGame()) { val simulatorJson = downloadEntity.getMetaExtra(Constants.SIMULATOR) val gameName = downloadEntity.getMetaExtra(Constants.GAME_NAME) if (simulatorJson.isEmpty()) return var simulator = GsonUtils.fromJson(simulatorJson, SimulatorEntity::class.java) val isInstalled = PackageUtils.isInstalledFromAllPackage( HaloApp.getInstance().application, simulator.apk?.packageName ) val isInstalledNewSimulator = SimulatorGameManager.isNewSimulatorInstalled(HaloApp.getInstance().application) val isInstalledOldSimulator = SimulatorGameManager.isOldSimulatorInstalled(HaloApp.getInstance().application) // if (!isInstalled && !isInstalledNewSimulator) { val currentActivity = AppManager.getInstance().currentActivity() ?: return val newSimulator = Config.getNewSimulatorEntitySetting() if ((!isInstalledOldSimulator && newSimulator != null && newSimulator.active) || isInstalledNewSimulator) { //如果没有安装任一旧的模拟器 或者下载了新模拟器 则使用新版本模拟器 simulator = newSimulator ?: simulator } SimulatorDownloadManager.getInstance().showDownloadDialog( currentActivity, simulator, SimulatorDownloadManager.SimulatorLocation.LAUNCH, downloadEntity.gameId, gameName, null ) // } SimulatorGameManager.recordDownloadSimulatorGame(downloadEntity.gameId, simulator.type) SimulatorGameManager.postPlayedGame(downloadEntity.gameId, downloadEntity.packageName) } else { val downloadType = downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE) // 是否是自动安装 val isAutoInstall = SPUtils.getBoolean(GameDownloadSettingFragment.AUTO_INSTALL_SP_KEY, true) if (downloadType == Constants.SIMULATOR_DOWNLOAD || isAutoInstall) { if (FileUtils.isEmptyFile(downloadEntity.path)) { Utils.toast(mApplication, R.string.install_failure_hint) downloadManager.cancel(downloadEntity.url) } else { if (PackageUtils.isCanLaunchSetup( mApplication, downloadEntity.path ) || downloadType == Constants.SMOOTH_GAME ) { downloadEntity.meta[Constants.MARK_ALREADY_TRIGGERED_INSTALLATION] = "YES" tryWithDefaultCatch { PackageInstaller.install(mApplication, downloadEntity, false) } } else { // 弹出卸载提示框 if (downloadEntity.isPlugin) { EventBus.getDefault().post( EBShowDialog( BaseActivity.PLUGGABLE, downloadEntity.path ) ) } else { EventBus.getDefault().post( EBShowDialog( BaseActivity.SIGNATURE_CONFLICT, downloadEntity.path ) ) } } } } } } // 统计下载完成 uploadData(gameId, downloadEntity.platform) } // 下载过程分析统计 // 部分设备 (已知 vivo 5.1.1) 在调用 packageManager.getPackageArchiveInfo 获取比较大的 APK 文件时会出现 ANR // 这里为了让它能用就不判断是否解析包错误了 if (PackageUtils.isDeviceUnableToHandleBigApkFile(downloadEntity.path)) { return } val pm = mApplication.packageManager val packageInfo = pm.getPackageArchiveInfo(downloadEntity.path, 0) if (packageInfo == null) { val downloadType = downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE) if (downloadType == Constants.SIMULATOR_DOWNLOAD) { val currentActivity = AppManager.getInstance().currentActivity() ?: return DialogUtils.showSimulatorParseErrorDialog( currentActivity, downloadEntity.gameId, downloadEntity.name, object : ConfirmListener { override fun onConfirm() { val simulator = HaloApp.get(downloadEntity.name, true) as? SimulatorEntity ?: return DownloadManager.getInstance().cancel(downloadEntity.url, true, true, false) SimulatorDownloadManager.getInstance() .showDownloadDialog( currentActivity, simulator, SimulatorDownloadManager.SimulatorLocation.SIMULATOR_GAME ) } }) } } } // 统计下载完成事件 private fun statDoneEvent(downloadEntity: DownloadEntity) { var type: ExposureUtils.DownloadType if (downloadEntity.isUpdate) { if (downloadEntity.isVGame()) { type = ExposureUtils.DownloadType.FUN_UPDATE } else { type = ExposureUtils.DownloadType.UPDATE if (downloadEntity.isPlugin) { type = ExposureUtils.DownloadType.PLUGIN_UPDATE } } } else { type = if (downloadEntity.isVGame()) { ExposureUtils.DownloadType.FUN_DOWNLOAD } else { ExposureUtils.DownloadType.DOWNLOAD } } if (downloadEntity.isPluggable) { type = ExposureUtils.DownloadType.PLUGIN_DOWNLOAD } val isPlatformRecommend = java.lang.Boolean.parseBoolean(downloadEntity.getMetaExtra(Constants.IS_PLATFORM_RECOMMEND)) ExposureUtils.logADownloadCompleteExposureEvent( GameEntity( id = downloadEntity.gameId, mName = downloadEntity.name.removeSuffix(Constants.GAME_NAME_DECORATOR), gameVersion = downloadEntity.versionName ?: "", isPlatformRecommend = isPlatformRecommend ), downloadEntity.platform, downloadEntity.exposureTrace, downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown", type ) DataCollectionUtils.uploadDownload(mApplication, downloadEntity, "完成") } private fun processHijack(downloadEntity: DownloadEntity) { // 删除任务 downloadEntity.status = DownloadStatus.cancel DownloadManager.getInstance().cancel(downloadEntity.url) // 弹出提示框 EventBus.getDefault().post(EBShowDialog(BaseActivity.DOWNLOAD_HIJACK)) // 记录链接被劫持 DataCollectionUtils.uploadHijack(mApplication, downloadEntity) // 上传劫持log DataLogUtils.uploadHijack(mApplication, downloadEntity) } // 统计下载 private fun uploadData(id: String, platform: String?) { val params = HashMap() params["game"] = id params["platform"] = platform ?: "" val body = RequestBody.create( MediaType.parse("application/json"), JSONObject(params as Map<*, *>).toString() ) RetrofitManager.getInstance().api.postDownload(body) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(Response()) } }