340 lines
14 KiB
Kotlin
340 lines
14 KiB
Kotlin
package com.gh.common.util
|
||
|
||
import android.app.Activity
|
||
import android.app.PendingIntent
|
||
import android.content.Context
|
||
import android.content.Intent
|
||
import android.content.pm.PackageInstaller.SessionCallback
|
||
import android.net.Uri
|
||
import android.os.Build
|
||
import android.util.Log
|
||
import android.view.View
|
||
import androidx.annotation.RequiresApi
|
||
import androidx.appcompat.app.AppCompatActivity
|
||
import androidx.core.content.FileProvider
|
||
import com.gh.common.dialog.InstallPermissionDialogFragment
|
||
import com.gh.common.xapk.XapkInstallReceiver
|
||
import com.gh.common.xapk.XapkInstaller
|
||
import com.gh.download.server.BrowserInstallHelper
|
||
import com.gh.gamecenter.BuildConfig
|
||
import com.gh.gamecenter.common.constant.Constants
|
||
import com.gh.gamecenter.common.utils.DialogHelper
|
||
import com.gh.gamecenter.common.utils.getExtension
|
||
import com.gh.gamecenter.common.utils.getMetaExtra
|
||
import com.gh.gamecenter.core.AppExecutor
|
||
import com.gh.gamecenter.core.utils.CurrentActivityHolder
|
||
import com.gh.gamecenter.core.utils.MD5Utils
|
||
import com.gh.gamecenter.core.utils.ToastUtils
|
||
import com.gh.gamecenter.vpn.VpnHelper
|
||
import com.gh.vspace.VHelper
|
||
import com.halo.assistant.HaloApp
|
||
import com.lightgame.download.DownloadEntity
|
||
import com.lightgame.download.FileUtils
|
||
import com.lightgame.utils.Utils
|
||
import java.io.File
|
||
|
||
object PackageInstaller {
|
||
|
||
/**
|
||
* 为了兼容java代码
|
||
*/
|
||
@JvmStatic
|
||
fun install(context: Context, downloadEntity: DownloadEntity?) {
|
||
downloadEntity?.let { install(context, downloadEntity, true) }
|
||
}
|
||
|
||
/**
|
||
* 启动安装应用程序并取消通知栏安装消息
|
||
*
|
||
* 兼容所有包安装(apk,xapk),后续可加上apks
|
||
*
|
||
* 主动点击安装如果是xapk则需要toast提示
|
||
*/
|
||
@JvmStatic
|
||
fun install(context: Context, downloadEntity: DownloadEntity, showUnzipToast: Boolean) {
|
||
val pkgPath = downloadEntity.path
|
||
val isXapk = XapkInstaller.XAPK_EXTENSION_NAME == pkgPath.getExtension()
|
||
val isVGame = downloadEntity.getMetaExtra(Constants.SMOOTH_GAME) == "true"
|
||
|
||
val currentActivity = CurrentActivityHolder.getCurrentActivity() ?: return
|
||
|
||
if (isVGame) {
|
||
VHelper.install(currentActivity, downloadEntity)
|
||
return
|
||
}
|
||
|
||
// 已知问题
|
||
// 1. 此处可能遇到 activity 是 WXEntryActivity,因为 WXEntryActivity 不是 AppCompatActivity 调不起弹窗
|
||
// 2. 当 activity 全部出栈,但是应用还在下载游戏,下载完会唤不起安装
|
||
if (currentActivity is AppCompatActivity && !currentActivity.isFinishing) {
|
||
InstallPermissionDialogFragment.show(currentActivity, downloadEntity) { isFromPermissionGrantedCallback ->
|
||
// 取消状态栏下载完成的通知,若存在
|
||
downloadEntity.meta[Constants.MARK_ALREADY_TRIGGERED_INSTALLATION] = "YES"
|
||
DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity)
|
||
|
||
// 安卓12后的设备,需要重启应用来继续解压 XAPK 文件
|
||
if (isFromPermissionGrantedCallback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||
val pm = context.packageManager
|
||
val intent = pm.getLaunchIntentForPackage(context.packageName)
|
||
val mainIntent = Intent.makeRestartActivityTask(intent!!.component)
|
||
context.startActivity(mainIntent)
|
||
Runtime.getRuntime().exit(0)
|
||
} else {
|
||
if (isXapk) {
|
||
XapkInstaller.install(context, downloadEntity, showUnzipToast)
|
||
} else {
|
||
install(context, downloadEntity.isPlugin, downloadEntity.path, downloadEntity)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 启动安装应用程序
|
||
*
|
||
* 除非特殊情况,请勿使用此方法进行应用安装操作
|
||
* 除非你已经确定该文件一定是Apk
|
||
*
|
||
* @param downloadEntity 仅用来记录日志用,非必须可为空
|
||
*/
|
||
@JvmStatic
|
||
fun install(context: Context, isPluggin: Boolean = false, pkgPath: String?, downloadEntity: DownloadEntity? = null) {
|
||
if (pkgPath.isNullOrEmpty()) {
|
||
ToastUtils.toast("下载文件异常")
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 判断是否需要使用浏览器来进行安装
|
||
if (BrowserInstallHelper.isUseBrowserToInstallEnabled()
|
||
&& BrowserInstallHelper.shouldUseBrowserToInstall()
|
||
) {
|
||
BrowserInstallHelper.downloadFile(pkgPath)
|
||
return
|
||
}
|
||
|
||
if (PackageUtils.isCanLaunchSetup(context, pkgPath)) {
|
||
val currentActivity = CurrentActivityHolder.getCurrentActivity()
|
||
|
||
if (VpnHelper.shouldUseVpn() && currentActivity is AppCompatActivity) {
|
||
turnOnVpnThenInstall(currentActivity, pkgPath, downloadEntity)
|
||
} else {
|
||
install(context, pkgPath)
|
||
}
|
||
} else {
|
||
if (isPluggin) {
|
||
DialogHelper.showPluginDialog(context) {
|
||
uninstall(context, pkgPath)
|
||
}
|
||
} else {
|
||
// 非插件化的同包名不同签名冲突
|
||
DialogHelper.showSignatureConflictDialog(context) {
|
||
uninstall(context, pkgPath)
|
||
}
|
||
}
|
||
}
|
||
} catch (e: Exception) {
|
||
Utils.toast(context, e.message)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 最终执行安装的方法
|
||
*/
|
||
private fun install(context: Context, pkgPath: String) {
|
||
HaloApp.put(Constants.LAST_INSTALL_GAME, pkgPath)
|
||
val installIntent = getInstallIntent(context, pkgPath)
|
||
context.startActivity(installIntent)
|
||
}
|
||
|
||
fun installMultiple(
|
||
context: Context,
|
||
pkgPath: String,
|
||
sessionId: Int = -1
|
||
) {
|
||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||
return
|
||
}
|
||
val installer = context.packageManager.packageInstaller
|
||
val session = installer.openSession(sessionId)
|
||
// 监听安装回调的组件,可以是Activity、Service或者是BroadcastReceiver
|
||
val intent = Intent(context, XapkInstallReceiver::class.java)
|
||
.also {
|
||
it.putExtra(XapkInstallReceiver.KEY_PACKAGE_PATH, pkgPath)
|
||
}
|
||
|
||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||
} else {
|
||
PendingIntent.FLAG_CANCEL_CURRENT
|
||
}
|
||
val pendingIntent = PendingIntent.getActivity(context, sessionId, intent, flags)
|
||
// 提交数据流并执行安装
|
||
session.commit(pendingIntent.intentSender)
|
||
}
|
||
|
||
/**
|
||
* 获取启动安装意图
|
||
*
|
||
* 除非特殊情况,请勿使用此方法进行应用安装操作
|
||
* 除非你已经确定该文件一定是Apk且该次安装无需检查签名情况
|
||
*/
|
||
@JvmStatic
|
||
fun getInstallIntent(context: Context, path: String): Intent {
|
||
var uri = Uri.fromFile(File(path))
|
||
val installIntent =
|
||
if (BrowserInstallHelper.isUseBrowserToInstallEnabledWithPackageMatched()) {
|
||
Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||
} else {
|
||
Intent(Intent.ACTION_VIEW)
|
||
}
|
||
if ("smartisan" == Build.MANUFACTURER) {
|
||
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||
}
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||
uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID, File(path))
|
||
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||
installIntent.setDataAndType(uri, "application/vnd.android.package-archive")
|
||
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||
} else {
|
||
// 应用内更新不加 FLAG_ACTIVITY_NEW_TASK 在模拟器上会出现安装完成后安装界面也一并消失的类似闪退的表现
|
||
// Application 上下文就更不用说了
|
||
val pkgName = PackageUtils.getPackageNameByPath(context, path)
|
||
if (pkgName == context.packageName || context !is Activity) {
|
||
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||
}
|
||
installIntent.setDataAndType(uri, "application/vnd.android.package-archive")
|
||
}
|
||
InstallUtils.getInstance()
|
||
.addInstall(PackageUtils.getPackageNameByPath(context, path))
|
||
return installIntent
|
||
}
|
||
|
||
/**
|
||
* 卸载应用
|
||
*/
|
||
@JvmStatic
|
||
fun uninstall(context: Context, path: String) {
|
||
// TODO 根据 path 获取包名可能出现获取不到的情况,看能不能改成直接用包名!
|
||
val packageName = PackageUtils.getPackageNameByPath(context, path)
|
||
uninstallForPackageName(context, packageName)
|
||
}
|
||
|
||
/**
|
||
* 根据包名卸载应用
|
||
*/
|
||
@JvmStatic
|
||
fun uninstallForPackageName(context: Context, pkn: String?) {
|
||
if (pkn.isNullOrEmpty()) return
|
||
|
||
val uninstallIntent = Intent()
|
||
uninstallIntent.action = Intent.ACTION_DELETE
|
||
uninstallIntent.addCategory(Intent.CATEGORY_DEFAULT)
|
||
if (context !is Activity) {
|
||
uninstallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||
}
|
||
uninstallIntent.data = Uri.parse("package:$pkn")
|
||
InstallUtils.getInstance().addUninstall(pkn)
|
||
context.startActivity(uninstallIntent)
|
||
}
|
||
|
||
@JvmStatic
|
||
fun getDownloadPathWithId(id: String, format: String?): String {
|
||
return FileUtils.getDownloadPath(
|
||
HaloApp.getInstance().application,
|
||
id + "." + getFileSuffixByFormat(format)
|
||
)
|
||
}
|
||
|
||
private fun getFileSuffixByFormat(format: String?): String {
|
||
return if (format == Constants.XAPK_FORMAT
|
||
|| format == Constants.XAPK_APKS_FORMAT) {
|
||
XapkInstaller.XAPK_EXTENSION_NAME
|
||
} else {
|
||
"apk"
|
||
}
|
||
}
|
||
|
||
@JvmStatic
|
||
fun createDownloadId(gameName: String?): String {
|
||
return MD5Utils.getContentMD5(gameName + "_" + System.currentTimeMillis())
|
||
}
|
||
|
||
/**
|
||
* 启动 VPN 然后安装应用 (若没有授权会先提醒授权 VPN ,若拒绝授权会回落到直接执行安装)
|
||
*/
|
||
private fun turnOnVpnThenInstall(currentActivity: AppCompatActivity,
|
||
pkgPath: String,
|
||
downloadEntity: DownloadEntity?) {
|
||
if (VpnHelper.isVpnPermissionGranted(currentActivity) == true) {
|
||
VpnHelper.startVpn(currentActivity) { shouldShowVpnError ->
|
||
if (shouldShowVpnError) {
|
||
ToastUtils.toast("安装防护功能启动失败")
|
||
}
|
||
install(currentActivity, pkgPath)
|
||
}
|
||
} else {
|
||
val isTheFirstTimeToShowVpnHintDialog = VpnHelper.isTheFistTimeToShowVpnHintDialog()
|
||
|
||
downloadEntity?.let {
|
||
NewFlatLogUtils.logVpnHintDialogShow(it.gameId, it.name)
|
||
}
|
||
|
||
// 将 VPN 提示弹窗标记为已读(已读后的下一次显示"不再提醒"按钮)
|
||
VpnHelper.setVpnHintDialogShowed()
|
||
if (!VpnHelper.shouldShowVpnHintDialog()) {
|
||
VpnHelper.startVpn(currentActivity) { shouldShowVpnError ->
|
||
if (shouldShowVpnError) {
|
||
ToastUtils.toast("安装防护功能启动失败")
|
||
}
|
||
install(currentActivity, pkgPath)
|
||
}
|
||
} else {
|
||
DialogHelper.showGuideDialog(
|
||
context = currentActivity,
|
||
title = "开启安装防护",
|
||
content = "建议您开启安装防护功能,该功能有助于帮助您更快的完成安装,避免因提示和置换导致的重复下载等问题,安装防护功能需要获取您的VPN权限",
|
||
confirmText = "立即授权开启防护",
|
||
cancelText = "不再提醒",
|
||
confirmClickCallback = {
|
||
VpnHelper.ignoreVpnHintDialog()
|
||
VpnHelper.startVpn(currentActivity) { shouldShowVpnError ->
|
||
if (shouldShowVpnError) {
|
||
ToastUtils.toast("安装防护功能启动失败")
|
||
}
|
||
install(currentActivity, pkgPath)
|
||
}
|
||
|
||
downloadEntity?.let {
|
||
NewFlatLogUtils.logVpnHintDialogClick(it.gameId, it.name, "立即授权")
|
||
}
|
||
},
|
||
cancelClickCallback = {
|
||
VpnHelper.ignoreVpnFunction()
|
||
install(currentActivity, pkgPath)
|
||
downloadEntity?.let {
|
||
NewFlatLogUtils.logVpnHintDialogClick(it.gameId, it.name, "不再提醒")
|
||
}
|
||
},
|
||
extraConfig = DialogHelper.Config(
|
||
showCloseIcon = true,
|
||
showAlternativeCancelStyle = !isTheFirstTimeToShowVpnHintDialog
|
||
),
|
||
uiModificationCallback = { binding, dialog ->
|
||
binding.cancelTv.visibility = View.GONE
|
||
binding.closeContainer.setOnClickListener {
|
||
install(currentActivity, pkgPath)
|
||
dialog.dismiss()
|
||
|
||
downloadEntity?.let {
|
||
NewFlatLogUtils.logVpnHintDialogClick(it.gameId, it.name, "关闭按钮")
|
||
}
|
||
}
|
||
}
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
} |