Fix Next Up channel launch sign in (#1886)

## Description
When parsing intents, not every path for authentication was checked, so
launching via the "Next Up" OS channel wouldn't check for a profile PIN.
This PR fixes that.

Additionally, the "Next Up" OS channel launch wasn't checking the
correct parameter to ensure the back stack was written properly.

### Related issues
Fixes #1883

### Testing
I manually test on the emulator with these test cases:
- No profile PIN, cold start
- No profile PIN, hot start
- Profile PIN, cold start
- Profile PIN, hot start
- No profile PIN, launch via "Next Up" channel
- No profile PIN, launch via intent
- Profile PIN, launch via "Next Up" channel
- Profile PIN, launch via intent

Also added unit tests for intent-based and regular launching which
covers those cases and more.

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-08-21 15:04:07 -04:00 • committed by GitHub
parent 39523aa6e2
commit 4b927e6b7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 736 additions and 34 deletions

View file

@ -29,49 +29,26 @@ class IntentService
Timber.v("Intent data: %s", intent.data)
val action = intent.action ?: intent.data?.host
if (
intent.getStringParam("type") == null &&
intent.getStringParam(INTENT_ITEM_TYPE) == null &&
(action == Intent.ACTION_MAIN || action.isNullOrBlank())
) {
// Normal app launch
return IntentResult.NoOp
}
val userId = intent.getStringParam("userId")?.toUUIDOrNull()
val serverId = intent.getStringParam("serverId")?.toUUIDOrNull()
if (userId != null && serverId != null) {
Timber.v("Intent switches user")
val user =
serverRepository.serverDao.getUser(serverId, userId)
?: return IntentResult.Error("User not found")
if (!user.isProtected) {
serverRepository.restoreSession(serverId, userId)
} else {
return IntentResult.Error("Cannot switch to specified user")
}
} else if (serverRepository.currentUser == null) {
Timber.v("No current user, so restoring last")
val appPrefs = userPreferencesService.flow.first().appPreferences
if (appPrefs.signInAutomatically) {
val userId = appPrefs.currentUserId.toUUIDOrNull()
val serverId = appPrefs.currentServerId.toUUIDOrNull()
if (userId != null && serverId != null) {
serverRepository.restoreSession(serverId, userId)
?: return IntentResult.Error("Error restoring user")
} else {
return IntentResult.Error("Could not auto sign-in, specify a server & user")
}
} else {
return IntentResult.Error("Auto sign-in not enabled, specify a server & user")
}
val result = prepare(intent)
if (result != null) {
return result
}
if (intent.getStringParam("type") != null) {
// Existence of this key implies launching from Play Next channel
if (intent.getStringParam(INTENT_ITEM_TYPE) != null) {
return getDestinationFromChannel(intent)?.let {
IntentResult.Target(
destinations = listOf(it),
addHomeToBackStack = false,
)
}
?: IntentResult.Error("Invalid parameters")
} ?: IntentResult.Error("Invalid parameters")
}
if (action == Intent.ACTION_SEARCH || action == "search") {
@ -130,6 +107,49 @@ class IntentService
return IntentResult.Target(destinations)
}
internal suspend fun prepare(intent: Intent): IntentResult? {
val appPrefs = userPreferencesService.flow.first().appPreferences
val userId = intent.getStringParam(INTENT_USER_ID)?.toUUIDOrNull()
val serverId = intent.getStringParam(INTENT_SERVER_ID)?.toUUIDOrNull()
return if (userId != null && serverId != null) {
Timber.v("Intent switches user")
val user = serverRepository.serverDao.getUser(serverId, userId)
if (user != null && !user.isProtected) {
serverRepository.restoreSession(serverId, userId)
?: IntentResult.Error("Error restoring user")
null
} else {
IntentResult.Error("Cannot switch to specified user")
}
} else {
val profileProtected =
serverRepository.current.value
?.user
?.isProtected == true
if (appPrefs.signInAutomatically && !profileProtected) {
Timber.v("No current user, so restoring last")
val userId = appPrefs.currentUserId?.toUUIDOrNull()
val serverId = appPrefs.currentServerId?.toUUIDOrNull()
if (userId != null && serverId != null) {
val current =
serverRepository.restoreSession(serverId, userId)
?: return IntentResult.Error("Error restoring user")
if (current.user.isProtected) {
IntentResult.Error("Could not auto sign-in, user is protected")
} else {
null
}
} else {
IntentResult.Error("Could not auto sign-in, specify a server & user")
}
} else {
IntentResult.Error("Auto sign-in not enabled or user is protected")
}
}
}
private fun Intent.getStringParam(key: String) = getStringExtra(key) ?: data?.getQueryParameter(key)
private fun Intent.getLongParam(key: String) =
@ -173,6 +193,8 @@ class IntentService
const val INTENT_EPISODE_NUMBER = "epNum"
const val INTENT_SEASON_NUMBER = "seaNum"
const val INTENT_SEASON_ID = "seaId"
const val INTENT_SERVER_ID = "serverId"
const val INTENT_USER_ID = "userId"
}
}