diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 70a270edf3..2dba2904cb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -136,9 +136,14 @@
android:name="com.gh.gamecenter.MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:launchMode="singleTask"
+ android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/AppCompatTheme.APP"
- android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
+ android:windowSoftInputMode="stateAlwaysHidden|adjustResize" >
+
+
+
+
{
+ VHelper.postOnInitialized(() -> {
+ if (VHelper.isInstalled(gamePackageName)) {
+ VHelper.launch(this, gamePackageName);
+ } else {
+ ToastUtils.showToast("应用已被卸载!");
+ }
+ return null;
+ });
+ }, 500);
+ break;
case KEY_MARKET_DETAILS:
redirectGameDetail(bundle.getString(KEY_DATA));
break;
diff --git a/app/src/main/java/com/gh/vspace/VDownloadManagerAdapter.kt b/app/src/main/java/com/gh/vspace/VDownloadManagerAdapter.kt
index 46b4c74d05..47464ef912 100644
--- a/app/src/main/java/com/gh/vspace/VDownloadManagerAdapter.kt
+++ b/app/src/main/java/com/gh/vspace/VDownloadManagerAdapter.kt
@@ -10,13 +10,11 @@ import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.databind.BindingAdapters
-import com.gh.gamecenter.feature.exposure.ExposureEvent
-import com.gh.gamecenter.feature.exposure.ExposureSource
import com.gh.common.exposure.IExposable
import com.gh.common.util.NewFlatLogUtils
-import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.download.DownloadManager
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
@@ -25,8 +23,8 @@ import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.utils.*
-import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.common.view.DrawableView
+import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.CurrentActivityHolder
@@ -36,8 +34,13 @@ import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.ItemVgameDownloadManagerBinding
import com.gh.gamecenter.databinding.PopupHistoryOptionBinding
import com.gh.gamecenter.feature.entity.GameEntity
+import com.gh.gamecenter.feature.exposure.ExposureEvent
+import com.gh.gamecenter.feature.exposure.ExposureSource
+import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.history.ManageOption
import com.gh.gamecenter.manager.PackagesManager
+import com.gh.vspace.menu.VDownLoadManagerItemMenuDialog
+import com.gh.vspace.shortcut.ShortcutManager
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus
import com.lightgame.utils.Utils
@@ -45,6 +48,7 @@ import org.json.JSONArray
class VDownloadManagerAdapter(
context: Context,
+ private val mFragmentManager: FragmentManager,
private val mViewModel: VDownloadManagerViewModel,
) : ListAdapter(context), IExposable {
@@ -56,6 +60,7 @@ class VDownloadManagerAdapter(
private var mPopWindow: PopupWindow? = null
private var mCurrentOption = ManageOption.OPTION_MANAGER
private var mPopupBinding: PopupHistoryOptionBinding? = null
+ private var mItemMenuDialog: VDownLoadManagerItemMenuDialog? = null
var selectItems = arrayListOf()
@@ -187,37 +192,29 @@ class VDownloadManagerAdapter(
if (autoPause) {
holder.binding.downloadBtn.performClick()
}
- DialogHelper.showDialog(
- holder.binding.root.context,
- "删除游戏",
- "单机类游戏被删除将可能导致本地存档、充值数据丢失,请确认后操作(网游类游戏删除不会影响游戏存档和充值数据)",
- "再等等",
- "删除",
- {
- if (autoPause) {
- holder.binding.downloadBtn.performClick()
- }
- },
- {
- runOnIoThread {
- val apk = gameEntity.getApk().firstOrNull()
- mViewModel.removeItem(apk?.url, apk?.packageName)
-
- AppExecutor.uiExecutor.executeWithDelay({
- mViewModel.load(LoadType.REFRESH)
- }, 200)
- }
- },
- uiModificationCallback = {
- it.cancelTv.setTextColor(R.color.theme_red.toColor(it.root.context))
- it.confirmTv.setTextColor(R.color.text_subtitle.toColor(it.root.context))
- },
- extraConfig = DialogHelper.Config(centerTitle = true)
- )?.setOnCancelListener {
- if (autoPause) {
- holder.binding.downloadBtn.performClick()
- }
+ //还在下载中的游戏直接弹出删除对话框
+ if (!mViewModel.isTypeDownloaded()) {
+ showDelGameDialog(holder, autoPause, gameEntity)
+ return@setOnLongClickListener false
}
+ //下载完成的在玩游戏才有菜单
+ if (mItemMenuDialog == null) {
+ mItemMenuDialog = VDownLoadManagerItemMenuDialog()
+ } else {
+ mItemMenuDialog?.dismiss()
+ }
+ mItemMenuDialog?.onAddIconToLauncherClickListener = {
+ //添加到桌面
+ addShortcutToLauncher(holder, gameEntity)
+ }
+ mItemMenuDialog?.onClearGameDataClickListener = {
+ //清除游戏数据
+ showCLearGameDataDialog(holder, gameEntity)
+ }
+ mItemMenuDialog?.onDeleteGameClickListener = {
+ showDelGameDialog(holder, autoPause, gameEntity)
+ }
+ mItemMenuDialog?.show(mFragmentManager, null)
return@setOnLongClickListener false
}
}
@@ -228,6 +225,73 @@ class VDownloadManagerAdapter(
}
}
+ /**
+ * 显示添加图标到桌面的对话框
+ */
+ private fun addShortcutToLauncher(holder: VGameItemViewHolder, gameEntity: GameEntity) {
+ ShortcutManager.getInstance().tryCreateShortCut(holder.binding.root.context, gameEntity)
+ }
+
+ /**
+ * 显示清除游戏数据对话框
+ */
+ private fun showCLearGameDataDialog(holder: VGameItemViewHolder, gameEntity: GameEntity) {
+ DialogHelper.showDialog(
+ holder.binding.root.context,
+ "提示",
+ "清除后游戏的所有记录都将被清空,无法恢复!您确定要清除吗?",
+ "确定",
+ "取消",
+ confirmClickCallback = { //清除游戏数据
+ mViewModel.cleanGameData(gameEntity.getUniquePackageName())
+ },
+ extraConfig = DialogHelper.Config(centerTitle = true)
+ )
+ }
+
+ /**
+ * 显示删除游戏的对话框
+ */
+ private fun showDelGameDialog(
+ holder: VGameItemViewHolder,
+ autoPause: Boolean,
+ gameEntity: GameEntity
+ ) {
+ DialogHelper.showDialog(
+ holder.binding.root.context,
+ "删除游戏",
+ "单机类游戏被删除将可能导致本地存档、充值数据丢失,请确认后操作(网游类游戏删除不会影响游戏存档和充值数据)",
+ "再等等",
+ "删除",
+ {
+ if (autoPause) {
+ holder.binding.downloadBtn.performClick()
+ }
+ },
+ {
+ //删除对应的桌面快捷方式
+ ShortcutManager.getInstance().removeShortcut(holder.binding.root.context, gameEntity)
+ runOnIoThread {
+ val apk = gameEntity.getApk().firstOrNull()
+ mViewModel.removeItem(apk?.url, apk?.packageName)
+
+ AppExecutor.uiExecutor.executeWithDelay({
+ mViewModel.load(LoadType.REFRESH)
+ }, 200)
+ }
+ },
+ uiModificationCallback = {
+ it.cancelTv.setTextColor(R.color.theme_red.toColor(it.root.context))
+ it.confirmTv.setTextColor(R.color.text_subtitle.toColor(it.root.context))
+ },
+ extraConfig = DialogHelper.Config(centerTitle = true)
+ )?.setOnCancelListener {
+ if (autoPause) {
+ holder.binding.downloadBtn.performClick()
+ }
+ }
+ }
+
private fun showOptionWindow() {
mPopupBinding = PopupHistoryOptionBinding.inflate(LayoutInflater.from(mContext))
mPopupBinding?.root?.isFocusable = true
diff --git a/app/src/main/java/com/gh/vspace/VDownloadManagerFragment.kt b/app/src/main/java/com/gh/vspace/VDownloadManagerFragment.kt
index da10742b94..6d036a1c22 100644
--- a/app/src/main/java/com/gh/vspace/VDownloadManagerFragment.kt
+++ b/app/src/main/java/com/gh/vspace/VDownloadManagerFragment.kt
@@ -27,6 +27,7 @@ class VDownloadManagerFragment :
private val mAdapter by lazy {
VDownloadManagerAdapter(
requireContext(),
+ childFragmentManager,
provideListViewModel()
)
}
diff --git a/app/src/main/java/com/gh/vspace/VDownloadManagerViewModel.kt b/app/src/main/java/com/gh/vspace/VDownloadManagerViewModel.kt
index 5ee1926e01..7d669d2586 100644
--- a/app/src/main/java/com/gh/vspace/VDownloadManagerViewModel.kt
+++ b/app/src/main/java/com/gh/vspace/VDownloadManagerViewModel.kt
@@ -6,6 +6,7 @@ import com.gh.download.DownloadManager
import com.gh.download.PackageObserver
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.utils.toProperReadableSize
+import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.ToastUtils
@@ -97,6 +98,12 @@ class VDownloadManagerViewModel(application: Application) :
}
}
+ fun cleanGameData(packageName: String?) {
+ runOnIoThread {
+ VHelper.cleanGameData(packageName)
+ }
+ }
+
@WorkerThread
fun removeItem(url: String?, packageName: String?) {
DownloadManager.getInstance().pause(url)
diff --git a/app/src/main/java/com/gh/vspace/VHelper.kt b/app/src/main/java/com/gh/vspace/VHelper.kt
index d983143b6f..9635ceccec 100644
--- a/app/src/main/java/com/gh/vspace/VHelper.kt
+++ b/app/src/main/java/com/gh/vspace/VHelper.kt
@@ -242,6 +242,15 @@ object VHelper {
}
}
+ @JvmStatic
+ fun postOnInitialized(callback: () -> Unit) {
+ if (mIsServiceConnected) {
+ callback()
+ } else {
+ mPendingAction = callback
+ }
+ }
+
private fun toastIfServiceConnectTimeout(timeout: Long = 3000L) {
if (!mIsServiceConnected) {
AppExecutor.uiExecutor.executeWithDelay({
@@ -616,6 +625,7 @@ object VHelper {
/**
* 启动应用
*/
+ @JvmStatic
fun launch(context: Context, packageName: String) {
Utils.log(LOG_TAG, "打开应用 $packageName")
@@ -692,6 +702,31 @@ object VHelper {
}
}
+ /**
+ * 清除游戏数据
+ */
+ @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(false, cleanGameDataClosure)
+ }
+ }
+
/**
* 获取游戏最后打开的时间
* @return 最后打开的时间戳
diff --git a/app/src/main/java/com/gh/vspace/menu/VDownLoadManagerItemMenuDialog.kt b/app/src/main/java/com/gh/vspace/menu/VDownLoadManagerItemMenuDialog.kt
new file mode 100644
index 0000000000..a0e8245152
--- /dev/null
+++ b/app/src/main/java/com/gh/vspace/menu/VDownLoadManagerItemMenuDialog.kt
@@ -0,0 +1,54 @@
+package com.gh.vspace.menu
+
+import android.os.Bundle
+import android.view.View
+import com.gh.gamecenter.R
+import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
+import com.gh.gamecenter.common.utils.setRootBackgroundDrawable
+import com.gh.gamecenter.common.utils.toColor
+import com.gh.gamecenter.common.utils.toDrawable
+import com.gh.gamecenter.databinding.DialogGameDownloadManagerMenuBinding
+
+/**
+ * @author : liujiarui
+ * date : 2023/3/20
+ * description : 畅玩游戏管理item菜单
+ */
+class VDownLoadManagerItemMenuDialog :
+ BaseBottomDialogFragment() {
+
+ var onAddIconToLauncherClickListener: (() -> Unit)? = null
+ var onClearGameDataClickListener: (() -> Unit)? = null
+ var onDeleteGameClickListener: (() -> Unit)? = null
+
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ mBinding.addIconToDesktopBtn.setOnClickListener {
+ dismissAllowingStateLoss()
+ onAddIconToLauncherClickListener?.invoke()
+ }
+ mBinding.clearGameDataBtn.setOnClickListener {
+ dismissAllowingStateLoss()
+ onClearGameDataClickListener?.invoke()
+ }
+ mBinding.uninstallGameBtn.setOnClickListener {
+ dismissAllowingStateLoss()
+ onDeleteGameClickListener?.invoke()
+ }
+ mBinding.cancelBtn.setOnClickListener {
+ dismissAllowingStateLoss()
+ }
+ }
+
+ override fun onDarkModeChanged() {
+ super.onDarkModeChanged()
+ mBinding.run {
+ root.setRootBackgroundDrawable(R.drawable.background_shape_white_radius_12_top_only)
+ cancelBtn.background = R.drawable.bg_shape_f5_radius_999.toDrawable(requireContext())
+ dialogTitleTv.setTextColor(R.color.text_title.toColor(requireContext()))
+ cancelBtn.setTextColor(R.color.text_subtitle.toColor(requireContext()))
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/vspace/shortcut/ShortcutManager.kt b/app/src/main/java/com/gh/vspace/shortcut/ShortcutManager.kt
new file mode 100644
index 0000000000..a472b8ca17
--- /dev/null
+++ b/app/src/main/java/com/gh/vspace/shortcut/ShortcutManager.kt
@@ -0,0 +1,206 @@
+package com.gh.vspace.shortcut
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Matrix
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import androidx.fragment.app.FragmentActivity
+import com.gh.gamecenter.MainActivity
+import com.gh.gamecenter.common.callback.BiCallback
+import com.gh.gamecenter.common.constant.EntranceConsts
+import com.gh.gamecenter.common.exposure.meta.MetaUtil
+import com.gh.gamecenter.common.utils.ImageUtils
+import com.gh.gamecenter.common.utils.PermissionHelper
+import com.gh.gamecenter.core.runOnUiThread
+import com.gh.gamecenter.core.utils.ToastUtils
+import com.gh.gamecenter.feature.entity.GameEntity
+import com.halo.assistant.HaloApp
+import com.lg.vspace.VirtualAppManager
+import com.muugi.shortcut.core.Executor
+import com.muugi.shortcut.core.Shortcut
+import com.muugi.shortcut.core.ShortcutAction
+
+/**
+ * @author : liujiarui
+ * date : 2023/3/20
+ * description : 动态创建快捷方式管理类
+ */
+class ShortcutManager private constructor() {
+
+ private val mDelegateManager by lazy { VirtualAppManager.get() }
+
+ private val mShortcutAction: ShortcutAction by lazy {
+ object : ShortcutAction() {
+ override fun showPermissionDialog(context: Context, check: Int, executor: Executor) {
+ runOnUiThread {
+ ShortcutPermissionDialog(context, executor).show()
+ }
+ }
+
+ override fun onCreateAction(
+ requestPinShortcut: Boolean,
+ check: Int,
+ executor: Executor
+ ) {
+ if (requestPinShortcut) {
+ ToastUtils.showToast("桌面图标创建成功~")
+ } else {
+ ToastUtils.showToast("创建快捷方式失败")
+ }
+ }
+
+ override fun onUpdateAction(updatePinShortcut: Boolean) {
+ if (updatePinShortcut) {
+ ToastUtils.showToast("桌面图标更新成功~")
+ } else {
+ ToastUtils.showToast("更新快捷方式失败")
+ }
+ }
+ }
+ }
+
+ private val mCallback: Shortcut.Callback by lazy {
+ object : Shortcut.Callback {
+ override fun onAsyncCreate(id: String, label: String) {
+ //ToastUtils.showToast("桌面图标创建成功~")
+ }
+ }
+ }
+
+
+ /**
+ * 创建快捷方式
+ */
+ fun tryCreateShortCut(context: Context, gameEntity: GameEntity) {
+ val activity = context as? FragmentActivity ?: return
+ if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
+ PermissionHelper.checkShortcutPermission(activity,
+ onGrantedCallback = {
+ createShortcut(context, gameEntity)
+ },
+ onDeniedCallback = {
+ ShortcutPermissionDialog(context).show()
+ })
+ } else {
+ ShortcutPermissionDialog(context).show()
+ }
+ }
+
+ /**
+ * 创建快捷方式
+ */
+ private fun createShortcut(context: Context, gameEntity: GameEntity) {
+ val gameId = gameEntity.id
+ val gameName = gameEntity.name ?: ""
+ val gameIcon = gameEntity.icon ?: ""
+ val dynamicShortcuts = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_PINNED)
+ val shortcutExists = dynamicShortcuts.find { it.id == gameId }
+ if (shortcutExists != null) {
+ ShortcutManagerCompat.enableShortcuts(
+ context, listOf(shortcutExists)
+ )
+ ToastUtils.showToast("桌面图标已创建,无需重复!")
+ return
+ }
+ ImageUtils.getBitmap(gameIcon, object : BiCallback {
+ override fun onFirst(first: Bitmap) {
+ // 压缩图片
+ val bitmap = compressImage(first)
+ val shortcutInfoIntent = getGameIntent(context, gameEntity)
+ shortcutInfoIntent.run {
+ val info = ShortcutInfoCompat.Builder(context, gameId)
+ .setIcon(IconCompat.createWithBitmap(bitmap))
+ .setShortLabel(gameName)
+ //.setAlwaysBadged() // 设置图标角标为应用角标
+ .setIntent(this)
+ .build()
+ // 创建快捷方式
+ Shortcut.singleInstance.requestPinShortcut(
+ context = context,
+ shortcutInfoCompat = info,
+ updateIfExit = false,
+ fixHwOreo = true,
+ shortcutAction = mShortcutAction
+ )
+ }
+ }
+
+ override fun onSecond(second: Boolean) {
+ ToastUtils.showToast("创建快捷方式失败")
+ }
+ })
+ }
+
+ /**
+ * 动态删除快捷方式
+ * 暂时只能在原生系统禁用快捷方式
+ */
+ fun removeShortcut(context: Context, gameEntity: GameEntity) {
+ val gameId = gameEntity.id
+ val gameName = gameEntity.name ?: ""
+ val dynamicShortcuts = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_PINNED)
+ val shortcutExists = dynamicShortcuts.find { it.id == gameId }
+ if (shortcutExists != null) {
+ ShortcutManagerCompat.disableShortcuts(
+ context, listOf(gameId), "$gameName 已经卸载"
+ )
+ ShortcutManagerCompat.removeLongLivedShortcuts(context, listOf(gameId))
+ }
+ }
+
+ /**
+ * 获取游戏快捷方式跳转Intent
+ * 直接启动游戏
+ */
+ private fun getIntent(gameEntity: GameEntity): Intent {
+ val intent = mDelegateManager.getStartGameIntent(
+ gameEntity.getUniquePackageName(),
+ gameEntity.id,
+ gameEntity.name ?: "unknown",
+ MetaUtil.getBase64EncodedAndroidId(),
+ HaloApp.getInstance().gid,
+ com.gh.gamecenter.BuildConfig.VERSION_NAME,
+ HaloApp.getInstance().channel
+ )
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ return intent
+ }
+
+ /**
+ * 获取游戏快捷方式跳转Intent
+ * 先启动光环助手,然后跳转游戏
+ */
+ private fun getGameIntent(context: Context, gameEntity: GameEntity): Intent {
+ val intent = Intent(context, MainActivity::class.java)
+ intent.action = Intent.ACTION_VIEW
+ intent.putExtra(EntranceConsts.KEY_REQUIRE_REDIRECT, true)
+ intent.putExtra(EntranceConsts.KEY_TO, EntranceConsts.HOST_LAUNCH_VM_GAME)
+ intent.putExtra(EntranceConsts.KEY_GAME_PKG, gameEntity.getUniquePackageName())
+ return intent
+ }
+
+ // Bitmap尺寸超过300*300就压缩
+ private fun compressImage(bitmap: Bitmap): Bitmap {
+ return if (bitmap.byteCount > 300 * 300 * 4) {
+ val width = bitmap.width.toFloat()
+ val height = bitmap.height.toFloat()
+ val matrix = Matrix()
+ val scaleWidth = 300 / width
+ val scaleHeight = 300 / height
+ matrix.postScale(scaleWidth, scaleHeight)
+ Bitmap.createBitmap(bitmap, 0, 0, width.toInt(), height.toInt(), matrix, true)
+ } else bitmap
+ }
+
+
+ companion object {
+ private val mShortcutManager = ShortcutManager()
+ fun getInstance(): ShortcutManager {
+ Shortcut.singleInstance.addShortcutCallback(mShortcutManager.mCallback)
+ return mShortcutManager
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/vspace/shortcut/ShortcutPermissionDialog.kt b/app/src/main/java/com/gh/vspace/shortcut/ShortcutPermissionDialog.kt
new file mode 100644
index 0000000000..e9ae5b3d2c
--- /dev/null
+++ b/app/src/main/java/com/gh/vspace/shortcut/ShortcutPermissionDialog.kt
@@ -0,0 +1,63 @@
+package com.gh.vspace.shortcut
+
+import android.app.Dialog
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.provider.Settings
+import android.view.Window
+import com.gh.common.util.DirectUtils
+import com.gh.gamecenter.R
+import com.gh.gamecenter.databinding.DialogGameAddShortcutPermissionBinding
+import com.muugi.shortcut.core.Executor
+
+/**
+ * @author : liujiarui
+ * date : 2023/3/20
+ * description : 创建快捷方式权限弹窗
+ */
+class ShortcutPermissionDialog(
+ context: Context,
+ private val mExecutor: Executor? = null
+) : Dialog(context, R.style.DialogWindowTransparent) {
+ private lateinit var mBinding: DialogGameAddShortcutPermissionBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ super.onCreate(savedInstanceState)
+ DialogGameAddShortcutPermissionBinding.inflate(layoutInflater).apply {
+ setContentView(root)
+ mBinding = this
+ }
+ initEvent()
+ }
+
+ private fun initEvent() {
+ mBinding.closeContainer.setOnClickListener {
+ dismiss()
+ }
+ mBinding.goSettingTv.setOnClickListener {
+ jumpToSetting()
+ dismiss()
+ }
+ mBinding.serviceTv.setOnClickListener {
+ jumpToFeedback()
+ dismiss()
+ }
+ }
+
+ private fun jumpToSetting() {
+ if (mExecutor != null) {
+ mExecutor.executeSetting()
+ return
+ }
+ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ intent.data = Uri.parse("package:" + context.packageName)
+ context.startActivity(intent)
+ }
+
+ private fun jumpToFeedback() {
+ DirectUtils.directToFeedback(context, "无法正常设置桌面快捷方式权限", hintType = "other")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/ic_game_add_shortcut_permission_content.webp b/app/src/main/res/drawable-xxhdpi/ic_game_add_shortcut_permission_content.webp
new file mode 100644
index 0000000000..8e78493e32
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_game_add_shortcut_permission_content.webp differ
diff --git a/app/src/main/res/layout/dialog_game_add_shortcut_permission.xml b/app/src/main/res/layout/dialog_game_add_shortcut_permission.xml
new file mode 100644
index 0000000000..f1023eb94b
--- /dev/null
+++ b/app/src/main/res/layout/dialog_game_add_shortcut_permission.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_game_download_manager_menu.xml b/app/src/main/res/layout/dialog_game_download_manager_menu.xml
new file mode 100644
index 0000000000..e34d4f13b2
--- /dev/null
+++ b/app/src/main/res/layout/dialog_game_download_manager_menu.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/module_common/src/main/java/com/gh/gamecenter/common/base/fragment/BaseBottomDialogFragment.kt b/module_common/src/main/java/com/gh/gamecenter/common/base/fragment/BaseBottomDialogFragment.kt
new file mode 100644
index 0000000000..6b6a1f8c51
--- /dev/null
+++ b/module_common/src/main/java/com/gh/gamecenter/common/base/fragment/BaseBottomDialogFragment.kt
@@ -0,0 +1,61 @@
+package com.gh.gamecenter.common.base.fragment
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.viewbinding.ViewBinding
+import com.gh.gamecenter.common.R
+import com.gh.gamecenter.common.utils.getSuperClassGenericType
+import com.gh.gamecenter.core.utils.DisplayUtils
+
+/**
+ * @author : liujiarui
+ * date : 2023/3/20
+ * description : 底部弹出dialog
+ */
+abstract class BaseBottomDialogFragment : BaseDialogFragment() {
+
+ protected lateinit var mBinding: T
+
+ private fun initViewBinding(inflater: LayoutInflater): T {
+ return this::class.java.getSuperClassGenericType().getMethod(
+ "inflate",
+ LayoutInflater::class.java,
+ ViewGroup::class.java,
+ Boolean::class.java
+ ).invoke(null, inflater, null, false) as T
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ initViewBinding(inflater).apply { mBinding = this }
+ return mBinding.root
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val createDialog = super.onCreateDialog(savedInstanceState)
+ createDialog.setCanceledOnTouchOutside(true)
+ val window = createDialog.window
+ window?.setGravity(Gravity.BOTTOM)
+ window?.setWindowAnimations(R.style.community_publication_animation)
+ return createDialog
+ }
+
+ override fun onStart() {
+ super.onStart()
+ val width = if (isWidthMatchParent()) DisplayUtils.getScreenWidth() else {
+ dialog?.window?.attributes?.width ?: ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+ val height = dialog?.window?.attributes?.height ?: ViewGroup.LayoutParams.WRAP_CONTENT
+ dialog?.window?.setLayout(width, height)
+ }
+
+ /**
+ * 是否宽度撑满屏幕
+ */
+ protected fun isWidthMatchParent(): Boolean {
+ return true
+ }
+}
\ No newline at end of file
diff --git a/module_common/src/main/java/com/gh/gamecenter/common/constant/EntranceConsts.java b/module_common/src/main/java/com/gh/gamecenter/common/constant/EntranceConsts.java
index 2b7713be0b..693b7d9ac7 100644
--- a/module_common/src/main/java/com/gh/gamecenter/common/constant/EntranceConsts.java
+++ b/module_common/src/main/java/com/gh/gamecenter/common/constant/EntranceConsts.java
@@ -68,6 +68,8 @@ public class EntranceConsts {
public static final String HOST_GAME_RATING_DETAIL = "game_rating_detail";
public static final String HOST_HELP_AND_FEEDBACK = "help_and_feedback";
public static final String HOST_LAUNCH_SIMULATOR_GAME = "launch_simulator_game";
+
+ public static final String HOST_LAUNCH_VM_GAME = "launch_vm_game";
public static final String HOST_HELP_DETAIL = "help_detail";
public static final String HOST_GAME_COLLECTION_DETAIL = "game_collection_detail";
public static final String HOST_GAME_COLLECTION_SQUARE = "game_collection_square";
@@ -117,6 +119,8 @@ public class EntranceConsts {
public static final String KEY_SEARCHKEY = "searchKey";
public static final String KEY_HINT = "hint";
public static final String KEY_GAME = "game";
+
+ public static final String KEY_GAME_PKG = "game_pkg";
public static final String KEY_GAME_ICON_URL = "gameIconUrl";
public static final String KEY_SHARECONTENT = "shareContent";
public static final String KEY_SUGGESTTYPE = "suggestType";
diff --git a/module_common/src/main/java/com/gh/gamecenter/common/utils/Extensions.kt b/module_common/src/main/java/com/gh/gamecenter/common/utils/Extensions.kt
index 2e0efd018c..01746bf438 100644
--- a/module_common/src/main/java/com/gh/gamecenter/common/utils/Extensions.kt
+++ b/module_common/src/main/java/com/gh/gamecenter/common/utils/Extensions.kt
@@ -68,6 +68,7 @@ import okhttp3.MediaType
import okhttp3.RequestBody
import org.json.JSONArray
import org.json.JSONObject
+import java.lang.reflect.ParameterizedType
import java.net.URI
import java.util.*
import java.util.concurrent.TimeUnit
@@ -1492,4 +1493,31 @@ fun List.colorRGBToHSV(): FloatArray {
fun Activity.getUniqueId(): String {
return System.identityHashCode(this).toString()
+}
+
+/**
+ * 通过反射,获得定义Class时声明的父类的范型参数的类型.
+ */
+fun Class<*>.getSuperClassGenericType(): Class {
+ return getSuperClassGenericType(0)
+}
+
+/**
+ * 通过反射,获得定义Class时声明的父类的范型参数的类型.
+ * @param index 获取第几个范型参数的类型
+ */
+@Suppress("UNCHECKED_CAST")
+fun Class<*>.getSuperClassGenericType(index: Int): Class {
+ var cls: Class<*>? = this
+ var genType = cls?.genericSuperclass
+ while (genType !is ParameterizedType) {
+ cls = cls?.superclass
+ requireNotNull(cls)
+ genType = cls.genericSuperclass
+ }
+ val params = genType.actualTypeArguments
+ require(!(index >= params.size || index < 0))
+ return if (params[index] !is Class<*>) {
+ throw IllegalArgumentException()
+ } else params[index] as Class
}
\ No newline at end of file
diff --git a/module_common/src/main/java/com/gh/gamecenter/common/utils/PermissionHelper.kt b/module_common/src/main/java/com/gh/gamecenter/common/utils/PermissionHelper.kt
index 7a83808a09..d3ca8346b9 100644
--- a/module_common/src/main/java/com/gh/gamecenter/common/utils/PermissionHelper.kt
+++ b/module_common/src/main/java/com/gh/gamecenter/common/utils/PermissionHelper.kt
@@ -342,4 +342,41 @@ object PermissionHelper {
}
}
+ /**
+ * 检查添加到桌面快捷方式权限
+ */
+ @SuppressLint("CheckResult")
+ @JvmStatic
+ fun checkShortcutPermission(
+ context: FragmentActivity,
+ onGrantedCallback: EmptyCallback,
+ onDeniedCallback: EmptyCallback
+ ) {
+ tryWithDefaultCatch {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ onGrantedCallback.onCallback()
+ return
+ }
+ val rxPermission = RxPermissions(context)
+ rxPermission
+ .requestEachCombined(
+ Manifest.permission.INSTALL_SHORTCUT,
+ Manifest.permission.UNINSTALL_SHORTCUT,
+ )
+ .subscribe { permission ->
+ when {
+ permission.granted -> {
+ onGrantedCallback.onCallback()
+ }
+ permission.shouldShowRequestPermissionRationale -> {
+ // do nothing
+ }
+ else -> {
+ onDeniedCallback.onCallback()
+ }
+ }
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/module_lib/build.gradle b/module_lib/build.gradle
index 84042c9450..e05a76fd6f 100644
--- a/module_lib/build.gradle
+++ b/module_lib/build.gradle
@@ -1,2 +1,3 @@
configurations.maybeCreate("default")
-artifacts.add("default", file('quick_login_android_5.9.4.aar'))
\ No newline at end of file
+artifacts.add("default", file('quick_login_android_5.9.4.aar'))
+artifacts.add("default", file('shortcut.aar'))
\ No newline at end of file
diff --git a/module_lib/shortcut.aar b/module_lib/shortcut.aar
new file mode 100644
index 0000000000..7256765360
Binary files /dev/null and b/module_lib/shortcut.aar differ
diff --git a/vspace-bridge b/vspace-bridge
index 066641c478..b00d19b246 160000
--- a/vspace-bridge
+++ b/vspace-bridge
@@ -1 +1 @@
-Subproject commit 066641c4787a03366e8d490ac5b43cca4133d499
+Subproject commit b00d19b246fb06894fb840b3e4251dd71959629b