implement deep links support

This commit is contained in:
Chloe committed 2025-12-26 17:37:22 -07:00
1 parent b69c915e51
commit 92d876560a
5 files changed
+131 -24

No files matched your search

+15
View File
@@ -1,3 +1,18 @@
# Geode Android Launcher # Geode Android Launcher
Launches a vanilla copy of Geometry Dash with the Geode loader added. Launches a vanilla copy of Geometry Dash with the Geode loader added.
## Shortcut Links
The launcher supports a handful of links as an escape hatch when the main functionality is impaired in some way (such as needing to prevent an automatic launch due to a crash).
You can open these links by copying them into the URL bar in some browsers (like Chrome), or through ADB.
| Link | Action |
|---------------------------------------------|--------------------------------------------------------------------------|
| geode-launcher://main | Opens to the main activity, forces the user to explicitly confirm launch |
| geode-launcher://main/launch | Opens to the game after the launcher is finished checking for updates |
| geode-launcher://main/launch?safe-mode=true | Opens to the game like before, but with safe mode enabled |
| geode-launcher://settings | Opens the main settings |
| geode-launcher://developer-settings | Opens the developer settings |
| geode-launcher://logs | Opens the application logs |
| geode-launcher://crashes | Opens the crash dumps viewer |
+68 -8
View File
@@ -57,19 +57,43 @@
android:theme="@style/Theme.GeodeLauncher" /> android:theme="@style/Theme.GeodeLauncher" />
<activity <activity
android:name=".preferences.DeveloperSettingsActivity" android:name=".preferences.DeveloperSettingsActivity"
android:exported="false" android:exported="true"
android:label="@string/title_activity_developer_settings" android:label="@string/title_activity_developer_settings"
android:theme="@style/Theme.GeodeLauncher" /> android:theme="@style/Theme.GeodeLauncher">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="developer-settings" />
</intent-filter>
</activity>
<activity <activity
android:name=".preferences.ApplicationLogsActivity" android:name=".preferences.ApplicationLogsActivity"
android:exported="false" android:exported="true"
android:label="@string/title_activity_application_logs" android:label="@string/title_activity_application_logs"
android:theme="@style/Theme.GeodeLauncher" /> android:theme="@style/Theme.GeodeLauncher">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="logs" />
</intent-filter>
</activity>
<activity <activity
android:name=".preferences.CrashLogsActivity" android:name=".preferences.CrashLogsActivity"
android:exported="false" android:exported="true"
android:label="@string/title_activity_crash_logs" android:label="@string/title_activity_crash_logs"
android:theme="@style/Theme.GeodeLauncher" /> android:theme="@style/Theme.GeodeLauncher">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="crashes" />
</intent-filter>
</activity>
<activity <activity
android:name=".preferences.ApplicationIconActivity" android:name=".preferences.ApplicationIconActivity"
android:exported="false" android:exported="false"
@@ -77,9 +101,17 @@
android:theme="@style/Theme.GeodeLauncher" /> android:theme="@style/Theme.GeodeLauncher" />
<activity <activity
android:name=".preferences.SettingsActivity" android:name=".preferences.SettingsActivity"
android:exported="false" android:exported="true"
android:label="@string/title_activity_settings" android:label="@string/title_activity_settings"
android:theme="@style/Theme.GeodeLauncher" /> android:theme="@style/Theme.GeodeLauncher">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="settings" />
</intent-filter>
</activity>
<activity <activity
android:name=".GeometryDashActivity" android:name=".GeometryDashActivity"
android:configChanges="orientation|screenSize|smallestScreenSize" android:configChanges="orientation|screenSize|smallestScreenSize"
@@ -101,6 +133,13 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="main" />
</intent-filter>
</activity> </activity>
<activity-alias <activity-alias
@@ -115,6 +154,13 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="main" />
</intent-filter>
</activity-alias> </activity-alias>
<activity-alias <activity-alias
android:name=".MainActivityPride" android:name=".MainActivityPride"
@@ -128,6 +174,13 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="main" />
</intent-filter>
</activity-alias> </activity-alias>
<activity-alias <activity-alias
android:name=".MainActivityGeode" android:name=".MainActivityGeode"
@@ -141,6 +194,13 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geode-launcher" android:host="main" />
</intent-filter>
</activity-alias> </activity-alias>
<provider <provider
@@ -96,12 +96,16 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge()
clearDownloadedApks(this)
val gdInstalled = GamePackageUtils.isGameInstalled(packageManager) val gdInstalled = GamePackageUtils.isGameInstalled(packageManager)
val geodeInstalled = LaunchUtils.isGeodeInstalled(this) val geodeInstalled = LaunchUtils.isGeodeInstalled(this)
if (gdInstalled && geodeInstalled && intent.getBooleanExtra("restarted", false)) {
onLaunch(this)
}
enableEdgeToEdge()
clearDownloadedApks(this)
val returnMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE) val returnMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE)
val returnExtendedMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE) val returnExtendedMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE)
@@ -115,8 +119,23 @@ class MainActivity : ComponentActivity() {
LoadFailureInfo(LaunchUtils.LauncherError.CRASHED) LoadFailureInfo(LaunchUtils.LauncherError.CRASHED)
} else { null } } else { null }
var launchArguments: LaunchArguments? = null
val intentData = intent.data
if (intent.action == Intent.ACTION_VIEW && intentData != null) {
val isLaunch = intentData.path == "/launch"
val safeMode = if (isLaunch) {
intentData.getBooleanQueryParameter("safe-mode", false)
} else false
launchArguments = LaunchArguments(
autoSafeMode = safeMode,
forcePause = !isLaunch,
forceLaunch = isLaunch,
)
}
val redirectToAlt = PreferenceUtils.get(this).getBoolean(PreferenceUtils.Key.DISABLE_REDESIGN) val redirectToAlt = PreferenceUtils.get(this).getBoolean(PreferenceUtils.Key.DISABLE_REDESIGN)
if (redirectToAlt) { if (redirectToAlt && launchArguments == null) {
val launchIntent = Intent(this, LegacyMainActivity::class.java) val launchIntent = Intent(this, LegacyMainActivity::class.java)
launchIntent.putExtras(intent) launchIntent.putExtras(intent)
startActivity(launchIntent) startActivity(launchIntent)
@@ -137,6 +156,10 @@ class MainActivity : ComponentActivity() {
launchViewModel.loadFailure = loadFailureInfo launchViewModel.loadFailure = loadFailureInfo
} }
if (launchArguments != null) {
launchViewModel.launchArguments = launchArguments
}
CompositionLocalProvider(LocalTheme provides theme) { CompositionLocalProvider(LocalTheme provides theme) {
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption, dynamicColor = !dynamicColorOption) { GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption, dynamicColor = !dynamicColorOption) {
Surface( Surface(
@@ -147,14 +170,6 @@ class MainActivity : ComponentActivity() {
} }
} }
} }
if (gdInstalled && geodeInstalled) {
intent.getBooleanExtra("restarted", false).let {
if (it) {
onLaunch(this)
}
}
}
} }
} }
@@ -605,7 +620,7 @@ fun GeodeUpdateIndicator(snackbarHostState: SnackbarHostState, onRetry: () -> Un
val context = LocalContext.current val context = LocalContext.current
val resources = LocalResources.current val resources = LocalResources.current
val releaseState = ReleaseManager.get(context).uiState.value val releaseState = ReleaseManager.get(context).uiState.collectAsState().value
if (releaseState !is ReleaseManager.ReleaseManagerState.Failure) { if (releaseState !is ReleaseManager.ReleaseManagerState.Failure) {
return return
@@ -659,7 +674,9 @@ fun AltMainScreen(
val launchUIState by launchViewModel.uiState.collectAsState() val launchUIState by launchViewModel.uiState.collectAsState()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
var launchInSafeMode by remember { mutableStateOf(false) } var launchInSafeMode by remember {
mutableStateOf(launchViewModel.launchArguments?.autoSafeMode == true)
}
var showErrorInfo by remember { mutableStateOf(false) } var showErrorInfo by remember { mutableStateOf(false) }
@@ -18,6 +18,12 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
data class LaunchArguments(
val autoSafeMode: Boolean,
val forcePause: Boolean,
val forceLaunch: Boolean
)
class LaunchViewModel(private val application: Application): ViewModel() { class LaunchViewModel(private val application: Application): ViewModel() {
companion object { companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory { val Factory: ViewModelProvider.Factory = viewModelFactory {
@@ -113,6 +119,8 @@ class LaunchViewModel(private val application: Application): ViewModel() {
val nextLauncherUpdate = ReleaseManager.get(application).availableLauncherUpdate val nextLauncherUpdate = ReleaseManager.get(application).availableLauncherUpdate
var loadFailure: LoadFailureInfo? = null var loadFailure: LoadFailureInfo? = null
var launchArguments: LaunchArguments? = null
private var hasCheckedForUpdates = false private var hasCheckedForUpdates = false
private var isCancelling = false private var isCancelling = false
private var hasManuallyStarted = false private var hasManuallyStarted = false
@@ -191,8 +199,15 @@ class LaunchViewModel(private val application: Application): ViewModel() {
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GEODE_NOT_FOUND)) return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GEODE_NOT_FOUND))
} }
// if forcing immediate launch, then act as if it's manually started (skips timers)
val forceImmediate = launchArguments?.forceLaunch == true
if (forceImmediate) {
hasManuallyStarted = true
}
val loadAutomatically = PreferenceUtils.get(application).getBoolean(PreferenceUtils.Key.LOAD_AUTOMATICALLY) val loadAutomatically = PreferenceUtils.get(application).getBoolean(PreferenceUtils.Key.LOAD_AUTOMATICALLY)
if (!loadAutomatically && !hasManuallyStarted) { val forcePause = launchArguments?.forcePause == true
if (!hasManuallyStarted && (forcePause || !loadAutomatically)) {
return cancelLaunch(true) return cancelLaunch(true)
} }
@@ -107,7 +107,7 @@ fun onOpenFileManager(context: Context) {
try { try {
context.startActivity(intent) context.startActivity(intent)
} catch (e: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
Toast.makeText( Toast.makeText(
context, context,
context.getText(R.string.no_activity_found), Toast.LENGTH_SHORT context.getText(R.string.no_activity_found), Toast.LENGTH_SHORT