Skip to content

[Android] Add ftr_ B/L telemetry markers for browser-login reason and login server type (W-23240736) - #2976

Open
wmathurin wants to merge 18 commits into
forcedotcom:devfrom
wmathurin:browser-login-telemetry-markers
Open

[Android] Add ftr_ B/L telemetry markers for browser-login reason and login server type (W-23240736)#2976
wmathurin wants to merge 18 commits into
forcedotcom:devfrom
wmathurin:browser-login-telemetry-markers

Conversation

@wmathurin

@wmathurin wmathurin commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds 8 new per-user `ftr_` telemetry markers to the SDK user-agent string (B2 deferred on Android):

  • B1, B3, B4: explain why browser login (Advanced Auth / Custom Tabs) was used
  • L1–L5: record which login server type was used on every non-refresh login

Follow-up to W-23126676 (Force Advanced Authentication flag). Spec and plan live in the Workspace repo under `specs/W-23701450-W-23240736-browser-login-telemetry-markers/`. Companion iOS PR: forcedotcom/SalesforceMobileSDK-iOS.

B-markers — "why browser login was used"

Registered per-user alongside `BW`. Exactly one B-marker per user. Priority: B3 > B4 > B1 (B2 deferred — no reliable MDM browser-login signal on Android).

Code Reason
`B1` Server auth-config opted in (`UseAndroidNativeBrowserForAuthentication`)
`B3` "Login for Admin" flow (`completedViaAdminCustomTab`)
`B4` `forceAdvancedAuthentication` SDK flag (W-23126676)

L-markers — "which login server type"

Registered per-user on every non-refresh login (browser or WebView). Exactly one L-marker per user.

Code Login server Detection
`L1` Production pool host matches `login.salesforce.com` or `login.*.salesforce.com` (no `.my.`) — covers internal envs like `login.test1.pc-rnd.salesforce.com`
`L2` Sandbox exact match `test.salesforce.com`
`L3` Welcome Discovery WD global flag set at auth-completion time (checked before cleared)
`L4` My Domain host contains `.my.` and ends with `.salesforce.com` — covers `.my.salesforce.com` and internal `.my.*.salesforce.com`
`L5` Other fallthrough

Implementation

  • `Features.java`: 8 new string constants (`FEATURE_BROWSER_LOGIN_`, `FEATURE_LOGIN_SERVER_`)
  • `LoginServerManager.java`: new `isProductionLoginServer()` and `isMyDomainServer()` static helpers; `isPoolServer()` updated to delegate to `isProductionLoginServer()`
  • `LoginActivity.kt`: `completedViaAdminCustomTab` field added; `selectBMarker()` and `selectLMarker()` internal functions (in companion object); both called in `onAuthFlowSuccess()` after BW promotion
  • `AuthFlowTesterPageObject.kt`: `validateUserAgent` extended with `expectedBMarker`/`expectedLMarker` params and assertions; B/L marker computation threaded through `restartAndValidateUser`, `switchToUserAndValidateUser`, `migrateAndValidate`, and `assertRevokeAndRefreshWorks`

Tests

  • `BrowserLoginTelemetryTest.kt` (11 tests): unit tests for `selectBMarker()` / `selectLMarker()` including internal env URLs — no device required
  • `LoginServerManagerTest.kt` (11 new tests): unit tests for `isProductionLoginServer()`, `isMyDomainServer()`, `isPoolServer()` including internal env URLs
  • `CAScopeSelectionLoginTests.kt` (3 new tests): `testCAOpaque_*_WebServerFlow_InAppWebView` variants that exercise `forceAdvancedAuthentication = false` (in-app WebView path) and verify no B-markers are registered
  • `AuthFlowTest.kt`: fixed B/L marker threading through `restartAndValidateUser`, `switchToUserAndValidateUser`, and `migrateAndValidate`; fixed missing `expectedBMarker` in `NegativeLoginTests` and `MultiUserLoginTests`

Notes

  • B2 (MDM) deferred on Android — no reliable signal distinguishes MDM-required browser login from other reasons

Add step-by-step Setup section, POST_NOTIFICATIONS permission
(Android 13+), SalesforceActionableNotificationContent full field
list, NotificationsApiClient, PushService subclassing guide,
React Native context, actionable notifications section, and
PushNotificationsRegistrationChangeWorkerTest in testing table.
… selection

- L3 is now FEATURE_LOGIN_SERVER_WELCOME_DISCOVERY (welcome.salesforce.com via WD flag)
- L4 is now FEATURE_LOGIN_SERVER_MY_DOMAIN (host ending in .my.salesforce.com)
- L5 remains the custom/fallthrough bucket
- My Domain detection now uses `.my.salesforce.com` host suffix instead of isPoolServer guard
- B2 (FEATURE_BROWSER_LOGIN_MDM) stays defined in Features.java for future use
  but is never selected on Android — MDM on Android forces cert auth (different code
  path that never sets completedViaBrowserTab); isMdmForcedBrowserLogin() is kept
  with an explanatory comment pending a real signal
- Android B-marker priority is now: B3 (admin) > B4 (force flag) > B1 (server auth config)
- Tests updated accordingly (B2 test expects B1 fallthrough; L3/L4 tests use new mapping)

Part of W-23240736
- Add 9 new feature constants in Features.java: B1-B4 and L1-L5
- Add isProductionLoginServer() and isMyDomainServer() to LoginServerManager
  covering internal-env URLs (login.*.salesforce.com, *.my.*.salesforce.com)
- Update isPoolServer() to delegate to isProductionLoginServer()
- Register B-marker (alongside FEATURE_BROWSER_LOGIN) and L-marker in onAuthFlowSuccess()
- Add BrowserLoginTelemetryTest and extend LoginServerManagerTest with helper tests
- B2 (MDM) defined but not registered on Android: MDM forces cert auth, not browser login
…-23240736)

Add expectedBMarker/expectedLMarker params to validateUserAgent in AuthFlowTesterPageObject.
Thread through validateUser(), loginAndValidate(), adminLoginAndValidate(), and
assertRevokeAndRefreshWorks(). All current browser-login tests expect B4+L4;
admin custom tab tests expect B3+L4; WD tests expect L3.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
1 Warning
⚠️ libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/LoginActivity.kt#L789 - Switch statement on an int with known associated constant missing case BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE

Generated by 🚫 Danger

…0736)

restartAndValidateUser was not computing expectedBMarker/expectedLMarker,
causing the "no B-markers present" assertion to fire for browser-login
tests that restart the app (e.g. DPoP restart test). B4 persists across
restart in per-user feature flags, so the assertion must expect it.
…(W-23240736)

MockK's JVMTI inline agent crashes on API 36 when retransforming LoginActivity
during BrowserLoginTelemetryTest class init. The two helpers are pure functions
with no Activity state, so moving them to the companion object lets tests call
them directly without requiring a mock of LoginActivity at all.
…ateAndValidate (W-23240736)

Both helpers were called without setting expectedBMarker, causing the validator
to assert no B-markers at all. But when forceAdvancedAuthentication=true (the
default), B4 is registered at login time and persists through user-switch and
migration. Apply the same expectedBMarker derivation already used in
assertRevokeAndRefreshWorks and restartAndValidateUser.
…inTests + AuthFlowTest (W-23240736)

The local switchToUserAndValidate helper, migrateAndValidate, and two direct
validateUser calls in MultiUserLoginTests all omitted expectedBMarker, causing
the validator to assert no B-markers are present even though B4 is registered
when forceAdvancedAuthentication=true.

Also add the missing isMultiUser parameter to migrateAndValidate so callers
where two users are logged in can pass isMultiUser=true.

Add a waitForAppLoad() before the post-SDK-logout validateUserAgent call in
testAdvancedAuthUser_HasBWFlag_RegularAuthUser_DoesNot to handle the async
UI re-render after the backgrounded logout completes.
…n details

Document the B-marker and L-marker ftr_ code families validated by
validateUserAgent, including priority rules, per-marker semantics, and
note that B2 is iOS-only. Also note B4 assertion in AdvancedAuthBeaconLoginTests
section. (W-23240736)
…ll (W-23240736)

validateUser call at line 125 omitted expectAdvancedAuth and expectedBMarker.
User logged in with forceAdvancedAuthentication=true so B4+BW are present.
@wmathurin

Copy link
Copy Markdown
Contributor Author

Local test run results

All tests run on API 36 emulator against the browser-login-telemetry-markers branch.

Unit tests

Suite Result
BrowserLoginTelemetryTest (SalesforceSDK) ✅ 18/18 passed
LoginServerManagerTest (SalesforceSDK) ✅ all passed

AuthFlowTester UI tests

Suite Result Notes
BootConfigLoginTests ✅ 4/4 passed
CAScopeSelectionLoginTests ✅ 8/8 passed
ECALoginTests ✅ passed Pre-existing Chrome Custom Tab timing flakiness on emulator; passed on retry
DPoPLoginTests ✅ passed Fix: added expectedBMarker=B4 to validateUser calls where forceAdvancedAuthentication=true was registering B4 but the validator expected no B-markers
RTRLoginTests ✅ 3/3 passed (1 @Ignored)
BeaconLoginTests ✅ 6/6 passed Pre-existing Chrome Custom Tab flakiness on first run; passed on retry
AdvancedAuthBeaconLoginTests ✅ 6/6 passed
LoginForAdminTests ✅ passed Pre-existing Custom Tab timing issue on emulator; passed on retry
WelcomeLoginTests ✅ 2/2 passed
LoginWithRestartTests ✅ 10/10 passed
NegativeLoginTests ✅ 3/3 passed Fix: added expectAdvancedAuth=true, expectedBMarker=B4 to testChangeDynamicConfigWithoutLogin_existingUserUnaffected
RefreshTokenMigrationTests ✅ 10/10 passed
MultiUserLoginTests ✅ passed Fix: added isMultiUser=true to migrateAndValidate call; added expectedBMarker derivation to switchToUserAndValidateUser; testAdvancedAuthUser_HasBWFlag_RegularAuthUser_DoesNot confirmed pre-existing timeout on original branch before our changes

CI failures on this PR

The 3 CI failures (LoginForAdminTests ×2, MultiUserLoginTests ×1) are pre-existing — identical failures appear in the dev nightly run from the same day (run 30887503809), before this branch was involved.

@wmathurin
wmathurin requested a review from brandonpage August 4, 2026 23:27
…without login

When the test (or a user) backs out of the auto-launched Custom Tab and then
proceeds with the in-app WebView, completedViaBrowserTab was left as true from
the tab launch. onAuthFlowSuccess() would then register BW per-user for what
was actually a WebView login, causing the InAppWebView UI tests to fail with
'Expected no BW flag'.

Reset the flag to false in CustomTabActivityResult.RESULT_CANCELED so the
subsequent WebView completion sees the correct state.
@wmathurin
wmathurin force-pushed the browser-login-telemetry-markers branch from f48f8b2 to 2c3daa5 Compare August 6, 2026 03:31
…ions changes auth surface

Resetting on RESULT_CANCELED was too broad: that callback fires during
server-change back-outs (ADVANCED_AUTH tests) where the next Custom Tab
should still register BW.

Resetting in clearWebView() was also too broad: clearWebView fires mid-
flow before Login Options opens (UAF path), wiping the flag before the
Custom Tab re-launches and sets it back.

The only moment when the auth surface genuinely changes is when Login
Options closes: loginDevMenuReload=true, which triggers onResume →
reloadWebView(). Reset completedViaBrowserTab=false there, before
reloadWebView() decides which surface to use:
- InAppWebView path: isBrowserLoginEnabled=false → reloadWebView() skips
  onBrowserCustomTabReady → flag stays false → BW not registered ✓
- UAF/forced-advanced-auth: isBrowserLoginEnabled=true → reloadWebView()
  calls loadLoginPageInCustomTab → flag set back to true → BW registered ✓
- ADVANCED_AUTH server-change: no Login Options → loginDevMenuReload never
  set → onResume reset never fires → unaffected ✓
@wmathurin
wmathurin force-pushed the browser-login-telemetry-markers branch from 2c3daa5 to b4c41a0 Compare August 6, 2026 03:48
…WebView path

When useWebServerFlow=false, adminLoginAndValidate intends to start with
the in-app WebView so it can reach the Login Options and Login for Admins
menu items. But ensureRegularAuthServer was called without
forceAdvancedAuthentication=false, so the SDK re-launched a Custom Tab
over the WebView after the server re-selection. The Compose hierarchy was
then behind the tab, causing openLoginOptions() to crash with
"No compose hierarchies found in the app".

Pass forceAdvancedAuthentication=useWebServerFlow so that the WebView
path also clears the forced-advanced-auth flag, matching the in-app
WebView surface that topBarPage (a plain LoginPageObject) expects to drive.
…ionPageObject

tapAllowInCustomTab and tapAllowInWebView used repeat(MAX_RETRIES=5) ×
500ms = 2.5s total to poll for the server-rendered Allow button. On FTL
(and locally) for second-user logins, Chrome is in a different pre-warmed
state and the button arrives after that window closes, so login never
completes and waitForAppLoad() times out.

Replace all three polling loops with a deadline-based while loop that
polls for the full TIMEOUT_MS budget (15s on FTL, 10s locally), matching
the budget every other waitForNode/waitForCustomTab call already uses.
Remove the now-unused MAX_RETRIES constant.

Fixes testAdvancedAuthUser_HasBWFlag_RegularAuthUser_DoesNot
(pre-existing failure on dev and on this branch).
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.

2 participants