Let a cancelled sign-in end the sign-in rather than the timeout

Backing out of the login page on Android left the shell showing "Opening your browser to
sign in…" with the button disabled for five minutes. Nothing was wrong except that nobody
told it: the redirect callback only ever completed when an intent arrived, so a user who
pressed back was waiting on OidcClient's browser timeout to expire before the flow failed
and the button came back.

There is no cancel event to subscribe to on this platform. Pressing back, dismissing the
browser and closing a provider's error page are indistinguishable from here — the browser
goes away and this application is foreground again with nothing delivered — so being
resumed while a sign-in is still waiting is the signal, and the only one there is. The
launcher records that a browser took the intent, OnResume fails the wait, and the guard
means the resumes that have nothing to do with signing in (a launch, recents, the
keystore's fingerprint prompt) go through untouched.

An exception rather than a cancellation, because OidcClient reads a cancelled wait as its
own timeout expiring and would report five minutes passing to somebody who waited two
seconds. It cannot steal a successful sign-in either: Android delivers the redirect to
OnNewIntent before resuming the activity, so the completion is already settled and the
attempt does nothing.

The enrollment key-binding trip through the browser is covered by the same change, since it
waits on the same callback.

The OnNewIntent remark had been sitting above OnResume, describing a method two below it.
Moved back, since the new remark wanted the space and the old one was wrong where it was.

Verified by building the head in Debug and Release. The behaviour itself is unverified for
the reason docs/android-port.md gives about this whole head: nothing has been run on a
device.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-01 21:10:37 +02:00
co-authored by Claude Opus 5
parent 08a820adcf
commit 1db8bed872
2 changed files with 71 additions and 8 deletions
+14 -6
View File
@@ -58,18 +58,19 @@ public sealed class MainActivity : AvaloniaMainActivity
{
/// <inheritdoc />
/// <remarks>
/// <c>OnNewIntent</c> rather than <c>OnCreate</c>, and that is what <c>SingleTask</c> above buys: the
/// activity is already running with a sign-in waiting inside it, so the redirect has to be delivered
/// into that instance. Any other launch mode would start a second copy of the activity — and with it a
/// second Avalonia application over a live one — leaving the original waiting for a response that had
/// already been consumed.
/// Resuming is also how a cancelled sign-in is noticed. There is no event for a user pressing back out
/// of the login page — the browser simply goes away and this application is foreground again with
/// nothing delivered — so being resumed while a sign-in is still waiting for its redirect is the
/// signal, and the only one there is. See <see cref="AndroidRedirectCallback.Abandon"/>, which is a
/// no-op unless a browser was opened and has not answered.
/// </remarks>
/// <inheritdoc />
protected override void OnResume()
{
base.OnResume();
PhoneEnvironment.CurrentActivity = this;
AndroidRedirectCallback.Abandon();
}
/// <inheritdoc />
@@ -88,6 +89,13 @@ public sealed class MainActivity : AvaloniaMainActivity
}
/// <inheritdoc />
/// <remarks>
/// <c>OnNewIntent</c> rather than <c>OnCreate</c>, and that is what <c>SingleTask</c> above buys: the
/// activity is already running with a sign-in waiting inside it, so the redirect has to be delivered
/// into that instance. Any other launch mode would start a second copy of the activity — and with it a
/// second Avalonia application over a live one — leaving the original waiting for a response that had
/// already been consumed.
/// </remarks>
protected override void OnNewIntent(Intent? intent)
{
base.OnNewIntent(intent);