diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 62ff02d129..0357ed9085 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -81,6 +81,12 @@ + + + + + + Icons.AutoMirrored.Filled.OpenInNew AppOptionMenuType.CreateShortcut -> Icons.AutoMirrored.Filled.AddToHomeScreen AppOptionMenuType.ExportFrontend -> Icons.Default.Share + AppOptionMenuType.CopyURI -> Icons.Default.Share AppOptionMenuType.RunContainer -> Icons.Default.PlayArrow AppOptionMenuType.EditContainer -> Icons.Default.Settings AppOptionMenuType.ResetToDefaults -> Icons.Default.RestartAlt @@ -368,6 +369,7 @@ private fun groupOptions(options: List): Map quickActions.add(option) // Game Management diff --git a/app/src/main/java/app/gamenative/utils/IntentLaunchManager.kt b/app/src/main/java/app/gamenative/utils/IntentLaunchManager.kt index 48afd0f25a..375c5b0b87 100644 --- a/app/src/main/java/app/gamenative/utils/IntentLaunchManager.kt +++ b/app/src/main/java/app/gamenative/utils/IntentLaunchManager.kt @@ -20,6 +20,9 @@ object IntentLaunchManager { private const val EXTRA_GAME_SOURCE = "game_source" private const val EXTRA_CONTAINER_CONFIG = "container_config" private const val ACTION_LAUNCH_GAME = "app.gamenative.LAUNCH_GAME" + private const val ACTION_VIEW = "android.intent.action.VIEW" + private const val URI_SCHEME = "gamenative" + private const val URI_HOST = "run" private const val MAX_CONFIG_JSON_SIZE = 50000 // 50KB limit to prevent memory exhaustion data class LaunchRequest( @@ -30,41 +33,60 @@ object IntentLaunchManager { fun parseLaunchIntent(intent: Intent): LaunchRequest? { Timber.d("[IntentLaunchManager]: Parsing intent: action=${intent.action}") - if (intent.action != ACTION_LAUNCH_GAME) { - Timber.d("[IntentLaunchManager]: Intent action '${intent.action}' doesn't match expected action '$ACTION_LAUNCH_GAME'") + if (intent.action != ACTION_LAUNCH_GAME && intent.action != ACTION_VIEW) { + Timber.d("[IntentLaunchManager]: Intent action '${intent.action}' doesn't match expected action '$ACTION_LAUNCH_GAME' or '$ACTION_VIEW'") return null } + var gameId: Int? = 0 + var gameSource: String? = "" + val containerConfigJson = if (intent.action == ACTION_LAUNCH_GAME) { + intent.getStringExtra(EXTRA_CONTAINER_CONFIG) + } else { + null + } + val containerConfig = if (containerConfigJson != null) { + try { + parseContainerConfig(containerConfigJson) + } catch (e: Exception) { + Timber.e(e, "[IntentLaunchManager]: Failed to parse container configuration JSON") + null + } + } else { + null + } - val gameId = intent.getIntExtra(EXTRA_APP_ID, -1) - Timber.d("[IntentLaunchManager]: Extracted app_id: $gameId from intent extras") + if (intent.action == ACTION_VIEW) { + if (intent.data?.scheme != URI_SCHEME || intent.data?.host != URI_HOST) { + Timber.d("[IntentLaunchManager]: URI '${intent.dataString}' doesn't match expected URI '$URI_SCHEME://$URI_HOST'") + return null + } + val data = intent.data + Timber.d(data?.getQueryParameterNames().toString()) + gameId = data?.getQueryParameter("appid")?.toIntOrNull() ?:0 + gameSource = data?.getQueryParameter("gamesource")?.uppercase(java.util.Locale.ROOT) + } - if (gameId <= 0) { + if (intent.action == ACTION_LAUNCH_GAME) { + gameId = intent.getIntExtra(EXTRA_APP_ID, -1) + Timber.d("[IntentLaunchManager]: Extracted app_id: $gameId from intent extras") + // Get Game Source for launch intent + gameSource = intent.getStringExtra(EXTRA_GAME_SOURCE)?.uppercase(java.util.Locale.ROOT) + } + + if (gameId == null || gameId <= 0) { Timber.w("[IntentLaunchManager]: Invalid or missing app_id in launch intent: $gameId") return null } - // Get Game Source for launch intent - var gameSource = intent.getStringExtra(EXTRA_GAME_SOURCE)?.uppercase(java.util.Locale.ROOT) val isValidGameSource = GameSource.entries.any { it.name == gameSource } if (!isValidGameSource) { - gameSource = GameSource.STEAM.name + Timber.w("[IntentLaunchManager]: Invalid or missing game source in launch intent: $gameSource") + return null } val appId = "${gameSource}_$gameId" Timber.d("[IntentLaunchManager]: Converted to appId: $appId") - val containerConfigJson = intent.getStringExtra(EXTRA_CONTAINER_CONFIG) - val containerConfig = if (containerConfigJson != null) { - try { - parseContainerConfig(containerConfigJson) - } catch (e: Exception) { - Timber.e(e, "[IntentLaunchManager]: Failed to parse container configuration JSON") - null - } - } else { - null - } - return LaunchRequest(appId, containerConfig) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8e432178dd..116a780c33 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1617,6 +1617,7 @@ Open store page Export for frontend + Copy web URI Open container Edit container Reset container