You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Android TV parity — Section B / PR 4: auth parity fixes. Brings the device-auth QR flow in line with tvOS across code expiry, and makes account-creation failures + the gated-modal open visible.
Commits:
Silent code rotation on expiry. tvOS treats EXPIRED_TOKEN as retryable — it transparently requests a fresh device code and re-renders the QR. Android's deviceAuthFlow treated anything but authorization_pending as terminal → the screen dropped to the error state and the shared sign-in path emitted a spurious user_signin_failed{error_code=expired_token} per expiry. Now, on expired_token, deviceAuthFlow requests a new code and keeps polling (no Error, no failure event), and SyncManagerImpl skips trackSignIn for expired_token (same as authorization_pending). Fixes both the Sign In and Create Account consumers. (Device auth is TV-only on Android, so this shared change has no phone impact.)
user_account_creation_failed. Shared trackSignIn emitted user_signin_failed regardless of isNewAccount. It now branches: a failed device-auth create-account emits UserAccountCreationFailedEvent(error_code); sign-in failures keep UserSigninFailedEvent. Also rethrows CancellationException in loginWithDeviceAuth's catch so a cancel mid-poll can't fire a stray failure event.
create_account_shown for the gated Follow modal. The account-gated Follow modal (TvCreateAccountModal, opened from podcast details) tracked nothing; it now fires create_account_shown{flow=account_encouragement} on open via CallOnce + a tiny TvCreateAccountModalViewModel. account_encouragement distinguishes the modal from the full-screen create-account (initial_onboarding).
Deliberate non-goal: the modal completion still emits user_signed_in (via isNewAccount=false) where iOS emits user_account_created — a reviewed decision on #5784 (device pairing can't truly distinguish create-vs-login). Left as-is.
Testing Instructions
debugProd TV build + adb logcat on LoggingAnalyticsListener.
Open Sign In (QR). Leave the code past its expiry (minutes) → the QR visibly rotates to a new code and keeps polling; no error screen, no user_signin_failed{expired_token} in logcat.
Create Account → force a pairing failure → user_account_creation_failed (not user_signin_failed).
From a podcast, tap Follow while signed out → the account modal opens → create_account_shown{flow=account_encouragement}.
Unit tests: expiry → fresh code requested (deviceAuthorize called twice), no Error, no failure event; create-account failure → creation-failed event; sign-in failure → signin-failed event; modal open → shown event.
The three changes are each well-scoped and the shared trackSignIn refactor correctly leaves the email/Google paths untouched (isNewAccount defaults to false, and handleLogin is the only other caller). Rethrowing CancellationException in loginWithDeviceAuth is a real fix — the old catch (Exception) both swallowed cancellation and fired a failure event. One concern with the rotation loop: making expired_token non-terminal in both deviceAuthFlow and trackSignIn removes every exit and every signal from that path, so a server that reports a fresh code as expired now produces a silent unbounded loop instead of an error screen.
Blocking
tv/src/main/java/au/com/shiftyjelly/pocketcasts/onboarding/signin/TvDeviceAuth.kt:18 — if the server returns expired_token for a code it just issued, the TV user sees the QR rotate every ~5s and can never pair, with no error screen and (because the same commit suppresses trackSignIn for expired_token) no analytics event; bound the rotations and emit TvSignInUiState.Error when exhausted. (thread)
Non-blocking
tv/src/main/java/au/com/shiftyjelly/pocketcasts/onboarding/createaccount/TvCreateAccountModalViewModel.kt:16 — the modal fires create_account_shown{account_encouragement} but TvPodcastDetailsViewModel.kt:103 passes isNewAccount = false, so its completion/failure are user_signed_in/user_signin_failed, leaving the new funnel with no joinable terminal event on either side. (thread)
modules/services/repositories/src/test/java/au/com/shiftyjelly/pocketcasts/repositories/sync/SyncManagerImplTest.kt — the two new tests cover the isNewAccount branch but nothing covers the expired_token suppression in loginWithDeviceAuth, which is the shared half of commit 1; a regression there silently restores one user_signin_failed{expired_token} per expiry for every TV sign-in attempt. The TvSignInViewModel test only proves the flow doesn't error.
Nits
modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/sync/SyncManagerImpl.kt:260 — "authorization_pending"/"expired_token" are duplicated as literals here while TvDeviceAuth.kt:12-13 holds them as constants; hoist to one shared place. (thread)
· feat/tv-auth-parity-analytics
No new commits since my previous review (HEAD is still 2ff9cb7), so there's nothing new to review. All three findings are still open in the current code.
Blocking
tv/src/main/java/au/com/shiftyjelly/pocketcasts/onboarding/signin/TvDeviceAuth.kt:18 — unbounded silent rotation when the server reports a just-issued code as expired; still unbounded, and SyncManagerImpl.kt:260 still suppresses the analytics signal. (thread) · Fix this →
Non-blocking
tv/src/main/java/au/com/shiftyjelly/pocketcasts/onboarding/createaccount/TvCreateAccountModalViewModel.kt:16 — the account_encouragement funnel has no joinable terminal event on either side. (thread)
modules/services/repositories/src/test/java/au/com/shiftyjelly/pocketcasts/repositories/sync/SyncManagerImplTest.kt — no test covers the expired_token suppression in loginWithDeviceAuth.
Nits
modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/sync/SyncManagerImpl.kt:260 — error-code literals still duplicated against TvDeviceAuth.kt:12-13. (thread)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Android TV parity — Section B / PR 4: auth parity fixes. Brings the device-auth QR flow in line with tvOS across code expiry, and makes account-creation failures + the gated-modal open visible.
Commits:
EXPIRED_TOKENas retryable — it transparently requests a fresh device code and re-renders the QR. Android'sdeviceAuthFlowtreated anything butauthorization_pendingas terminal → the screen dropped to the error state and the shared sign-in path emitted a spurioususer_signin_failed{error_code=expired_token}per expiry. Now, onexpired_token,deviceAuthFlowrequests a new code and keeps polling (noError, no failure event), andSyncManagerImplskipstrackSignInforexpired_token(same asauthorization_pending). Fixes both the Sign In and Create Account consumers. (Device auth is TV-only on Android, so this shared change has no phone impact.)user_account_creation_failed. SharedtrackSignInemitteduser_signin_failedregardless ofisNewAccount. It now branches: a failed device-auth create-account emitsUserAccountCreationFailedEvent(error_code); sign-in failures keepUserSigninFailedEvent. Also rethrowsCancellationExceptioninloginWithDeviceAuth's catch so a cancel mid-poll can't fire a stray failure event.create_account_shownfor the gated Follow modal. The account-gated Follow modal (TvCreateAccountModal, opened from podcast details) tracked nothing; it now firescreate_account_shown{flow=account_encouragement}on open viaCallOnce+ a tinyTvCreateAccountModalViewModel.account_encouragementdistinguishes the modal from the full-screen create-account (initial_onboarding).Deliberate non-goal: the modal completion still emits
user_signed_in(viaisNewAccount=false) where iOS emitsuser_account_created— a reviewed decision on #5784 (device pairing can't truly distinguish create-vs-login). Left as-is.Testing Instructions
debugProdTV build +adb logcatonLoggingAnalyticsListener.user_signin_failed{expired_token}in logcat.user_account_creation_failed(notuser_signin_failed).create_account_shown{flow=account_encouragement}.Unit tests: expiry → fresh code requested (deviceAuthorize called twice), no Error, no failure event; create-account failure → creation-failed event; sign-in failure → signin-failed event; modal open → shown event.
Fixes POC-857 https://linear.app/a8c/issue/POC-875/qr-code-exipration-account-creation-failed-event-reporting
Screenshots or Screencast
QR rotation is time-based (minutes) — best verified on device per the steps above; no static UI change to screenshot.
Checklist
./gradlew spotlessApply)