Skip to content

Keep the Android auth tab out of the app's task - #3

Merged
simonhamp merged 1 commit into
mainfrom
fix/android-auth-tab-task
Oct 21, 2026
Merged

simonhamp merged 1 commit into
mainfrom
fix/android-auth-tab-task

Conversation

@simonhamp

@simonhamp simonhamp commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

On Android, Browser::auth() opened its Custom Tab straight from MainActivity, so the tab joined the app's task. That's fine while everything stays alive. But if the app's activities are removed while the tab is open (a Play Store update, a force-stop, or Android killing the process in the background), the tab is left in the app's task on top of nothing. The launcher keeps bringing that task back, so tapping the app icon shows the old sign-in page and the app never starts. We hit this with the dev/null app on a Moto g24.

Approving was never the problem. Chrome and Firefox both send the redirect back with CLEAR_TOP, which closes the tab. It only goes wrong when the tab is still open when the app goes away.

What changed

Browser.OpenAuth now starts AuthTabActivity, an invisible activity in a separate document task, and that activity opens the Custom Tab on top of itself. The launcher never resumes that task, so the app icon always opens the app.

AuthTab then ends the sign-in roughly the way iOS's ASWebAuthenticationSession does:

  • any deep link into the app (normally the redirect) removes the tab's task
  • closing the tab (✕ or back) removes it and shows the app again
  • coming back to the app while the sign-in is open brings the tab back to the front
  • calling auth() again replaces the old tab
  • a new app process removes tabs left over from the previous one, since their sign-in is gone

nativephp.json gets an android.init_function and an android.activities entry, with a test that both exist. The README explains the Android behaviour. iOS, inApp() and open() are unchanged.

What to watch for

  • While a sign-in is open, Recents shows two cards for the app: the app and the sign-in page.
  • Coming back through the icon shows the app briefly (about 170 ms on the emulator) before the tab returns to the front.
  • Any deep link ends the sign-in, not only the callback scheme. iOS only finishes on NATIVEPHP_DEEPLINK_SCHEME, but on Android the redirect can be an https app link, and the plugin can't tell that apart from other links.
  • After an update or force-stop, the old sign-in page is still a card in Recents until the app next starts, because nothing of ours is running to remove it. The app's own card and the icon both open the app.
  • init_function needs nativephp/mobile 3.2 or later. On older cores auth() subscribes the listeners itself, so only the cleanup at startup is missing.

Reproduction

Android 15 emulator with Chrome 124, dev/null app, and a local stand-in for dev-null.sh whose Approve button answers with a 303 to devnull://auth/callback, like the real page.

  1. Open the app from its icon, tap Sign in, wait for the approve page.
  2. Press Home.
  3. Then one of: adb install -r the same APK (update), adb shell am force-stop <id>, or adb shell am kill <id> (background process death).
  4. Tap the app icon.
Before After
Update, then icon Approve page, app process not running App opens, sign-in task removed
Force-stop, then icon "Activity not started, its current task has been brought to the front", approve page, app not running App opens
Background kill, then icon Approve page, app not running App opens
Approve Signed in, tab closed Same
Close the tab Back in the app Same, sign-in task removed
Home, then icon, mid sign-in Tab Tab (brought back to the front)
Switch apps, back through Recents, approve Works Works

Update, then icon. Before on the left, after on the right:

Recents while a sign-in is open, after this change:

The Moto g24 (Android 14, Chrome 153) failed the same way. A sign-in tab left open over an adb install became the only activity in the app's task, and later launches showed it.

On the Moto with this branch (profileable build, opened from the app drawer):

  • Background kill, force-stop and update with the tab open: the icon opens the app, and the old sign-in task is gone once it starts.

  • Closing the tab returns to the app and leaves nothing in Recents.

  • Coming back through the icon mid sign-in: the tab is moved back 33 ms after the app resumes, and showing 137 ms later.

  • A full sign-in against a local copy of dev-null.sh over a tunnel (production was in maintenance): the real "Approve devnull on moto g24?" page, Approve, the callback arrives with NEW_TASK|CLEAR_TOP, the app signs in, and nothing is left in Recents.

Not tried with a non-Chrome browser.

The plugin suite passes locally with CI's composer setup (59 tests), and Pint is clean.

🤖 Generated with Claude Code

Browser::auth() opened its Custom Tab straight from MainActivity, so the tab joined the app's task. If the app's activities were removed while the tab was open (an update, a force-stop, or the process dying in the background), the launcher kept bringing that task back. The user saw the old sign-in page and the app never started.

The tab now opens from a small translucent AuthTabActivity started as its own document task, which the launcher never resumes. An init function and the existing lifecycle events end the sign-in: any deep link into the app removes the tab's task, closing the tab returns to the app, and coming back to the app while the sign-in is open brings the tab back to the front. A new process removes tabs left over from an old one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonhamp
simonhamp marked this pull request as ready for review October 21, 2026 15:27
@simonhamp
simonhamp merged commit 7014c7a into main Oct 21, 2026
2 checks passed
@simonhamp
simonhamp deleted the fix/android-auth-tab-task branch October 21, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant