Skip to content

refactor: replace the lock screen with a native privacy cover - #37256

Draft
Cal-L wants to merge 2 commits into
mainfrom
refactor/940-privacy-screen
Draft

Cal-L wants to merge 2 commits into
mainfrom
refactor/940-privacy-screen

Conversation

@Cal-L

@Cal-L Cal-L commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Description

The lock screen route is gone. Backgrounding the app now shows a native privacy cover (theme fill and the splash fox) and keeps it up until resume authentication finishes or the wallet is still unlocked and can continue.

Auto-lock is decided on return from wall-clock time. Immediate lock (0) locks on background. Timed locks lock only if enough time has passed when the user comes back. Never (-1) does not lock. Deeplinks that arrive during that decision wait until it finishes.

On Android, the biometric or device-credential prompt starts only after onPostResume. Leaving again before the prompt appears cancels that attempt, and the next resume starts a new one. iOS starts the prompt when the app becomes active. The cover is not shown for iOS inactive (app switcher, Control Center, Face ID).

Android 13+ blanks the Recents card with setRecentsScreenshotEnabled(false). Android 12 and older hold FLAG_SECURE from login until logout.

Card and Ramp still pause auto-lock during verification handoff. Those methods are named dangerousPauseAutoLock / dangerousResumeAutoLock and marked deprecated. The cover still shows. Navigation persistence should replace them.

Changelog

CHANGELOG entry: Added a privacy cover while the app is in the background and prompt unlock on return

Related issues

Fixes:

Refs: 940 privacy screen. No matching GitHub issue.

Manual testing steps

Feature: privacy cover and resume unlock

  Background:
    Given the user is logged in
    And auto-lock is set to Immediately

  Scenario: cover appears only after the app is fully backgrounded
    When the user opens the app switcher or Control Center on iOS
    Then the privacy cover is not shown
    When the user backgrounds the app
    Then the privacy cover is shown
    And the wallet contents are not visible

  Scenario: resume prompts unlock and then dismisses the cover
    Given the app is backgrounded and the privacy cover is showing
    When the user returns to the app
    Then a biometric or device-credential prompt is shown
    When authentication succeeds
    Then the privacy cover is dismissed
    And the user is back on the screen they left

  Scenario: leaving again before the prompt appears still unlocks on the next return
    Given auto-lock is Immediate on Android
    When the user backgrounds the app, returns, and backgrounds again before the prompt appears
    And the user returns once more
    Then a new authentication prompt is shown
    And the privacy cover stays up until authentication finishes

  Scenario: timed lock does not lock before the timeout
    Given auto-lock is set to 30 seconds
    When the user backgrounds the app and returns before 30 seconds
    Then the wallet stays unlocked
    And the privacy cover is dismissed
    When the user backgrounds the app and returns after 30 seconds
    Then an authentication prompt is shown

  Scenario: never lock still covers the app
    Given auto-lock is set to Never
    When the user backgrounds the app and returns
    Then the privacy cover was shown while backgrounded
    And the wallet stays unlocked without an authentication prompt

  Scenario: a deeplink waits until unlock finishes
    Given auto-lock is Immediate
    And the app is backgrounded
    When a deeplink is delivered and the user returns and unlocks
    Then the deeplink is handled after unlock

Screenshots/Recordings

Before

N/A

After

iOS and Android recordings to be added.

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
  • I've tested with a power user scenario
    • Use these power-user SRPs to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

For performance guidelines and tooling, see the Performance Guide.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Backgrounding should hide the wallet immediately, and resume authentication has to wait until Android will actually show the prompt.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Cal-L Cal-L self-assigned this Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Smart E2E Test Selection

Selected E2E tags: ALL

Selected Performance tags

@PerformanceLaunch
@PerformanceLogin

AI Confidence: 100

E2E reasoning

Expand to read

This PR is a major refactoring of the app lock/privacy screen system that touches critical authentication and app lifecycle paths:

  1. LockScreen route removed: The LockScreen React Native component and Routes.LOCK_SCREEN are completely deleted. This was previously used as a navigation guard in SDK Connect, Card routes, Ramp routes, and ProtectWalletMandatoryModal. Any E2E test that relied on the lock screen route behavior could be affected.

  2. New AppLockService (537 lines): Replaces both LockManagerService and AppStateService. This new service handles app state monitoring, auto-lock logic, privacy cover display, and biometric unlock prompts. It's initialized in index.js at app startup (critical path).

  3. New native PrivacyCover modules (iOS Swift + Android Kotlin): Native overlay shown when app backgrounds. This changes how the privacy screen works at the native level, affecting all platforms.

  4. Authentication.tryBiometricUnlock: New method extracted from sagas into Authentication service, changing the biometric unlock flow.

  5. Sagas refactored: appLockStateMachine and appStateListenerTask removed from Redux sagas; now handled by AppLockService. This changes the fundamental state machine for lock/unlock.

  6. SDK Connect changes: Routes.LOCK_SCREEN removed from permission/connection wait conditions, changing when dApp connections are allowed.

  7. Card/Ramp routes: Updated to use AppLockService.dangerousPauseAutoLock() instead of LockManagerService.stopListening().

The lock/unlock mechanism is fundamental to the entire app. Every E2E test flow involves the app being in an unlocked state, and changes to how locking/unlocking works can break any test. The removal of the LockScreen route, the new native privacy cover, and the refactored authentication flow all represent critical changes that could affect:

  • Login/unlock flows (SmokeAccounts, SmokeSeedlessOnboarding)
  • All wallet flows that require being unlocked
  • dApp connection flows (SmokeMMConnect, SmokeMultiChainAPI, SmokeNetworkExpansion)
  • Card/Ramp flows (SmokeMoney)
  • Transaction confirmations (SmokeConfirmations)
  • All other flows

The hard rule seed already selected ALL tags, and this analysis confirms that selection is appropriate given the critical nature of these changes.

Performance reasoning

Expand to read

The PR changes app startup (index.js now calls AppLockService.initialize() before Engine), which could affect cold start time and time-to-interactive (@PerformanceLaunch). The refactored lock/unlock mechanism (new AppLockService replacing LockManagerService + AppStateService, new native PrivacyCover modules, biometric unlock flow changes) could affect login and unlock performance (@PerformanceLogin). These are the most directly impacted performance scenarios.

View run

@Cal-L
Cal-L deployed to build-e2e October 7, 2026 03:44 — with GitHub Actions Active
@Cal-L
Cal-L deployed to build-e2e October 7, 2026 03:44 — with GitHub Actions Active
@Cal-L
Cal-L deployed to build-e2e October 7, 2026 05:42 — with GitHub Actions Active
@Cal-L
Cal-L deployed to build-e2e October 7, 2026 05:42 — with GitHub Actions Active
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Performance Test Results

ℹ️ Performance test results are currently non-blocking and will not block this PR.

❌ 2 tests failed · 6 tests · 1 device

📱 Devices tested (1)

Android: Google Pixel 8 Pro (v14.0)

❌ Failed Tests (2)

🔬 App profiling vs main is included under each failed scenario that has a prior baseline.

@assets-dev-team

Asset View, SRP 1 + SRP 2 + SRP 3

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 37576952055 · Baseline (last green on main) run 36822962196 @ 1bddfa4

Summary: ⚠️ 4 metrics over +10%: Memory avg (+75.94 (+12.6%)), Memory max (+83.82 (+11.1%)), Slow frames (+6.13 (+18.7%)), Critical issues (+1 (+100%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 10.4% 8.97% -1.43 (-13.8%)
CPU max 22.58% 20.69% -1.89 (-8.4%)
Memory avg 601.04 MB 676.98 MB +75.94 (+12.6%) ⚠️
Memory max 754.77 MB 838.59 MB +83.82 (+11.1%) ⚠️
Slow frames 32.77% 38.9% +6.13 (+18.7%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 3 3 0 (0%)
Critical issues 1 2 +1 (+100%) ⚠️
App size 403.23 MB 402.86 MB -0.37 (-0.1%)

@metamask-mobile-platform

Measure Warm Start: Warm Start to Login Screen

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) Quality gates exceeded 📹 Watch

🔬 App profiling check · Current run 37576952055 · Baseline (last green on main) run 37271155053 @ 8eac44c

Summary: ⚠️ 2 metrics over +10%: Slow frames (+25.38 (+429.4%)), Issues (+1 (+50%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 6.97% 7.34% +0.37 (+5.3%)
CPU max 21.23% 21.61% +0.38 (+1.8%)
Memory avg 536.83 MB 568.79 MB +31.96 (+5.9%)
Memory max 677.91 MB 730.56 MB +52.65 (+7.8%)
Slow frames 5.91% 31.29% +25.38 (+429.4%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 3 +1 (+50%) ⚠️
Critical issues 1 1 0 (0%)
App size 403.41 MB 402.86 MB -0.55 (-0.1%)
✅ Passed Tests (4)
Test Platform Device Duration Team Recording
Cold Start: Measure ColdStart To Login Screen Android Google Pixel 8 Pro (v14.0) 2.74s @metamask-mobile-platform 📹 Watch
Measure Warm Start: Login To Wallet Screen Android Google Pixel 8 Pro (v14.0) 0.00s @metamask-mobile-platform 📹 Watch
Rewards tab time-to-content (onboarding or dashboard) Android Google Pixel 8 Pro (v14.0) 0.60s @performance-team 📹 Watch
Measure Cold Start To Onboarding Screen Android Google Pixel 8 Pro (v14.0) 0.30s @metamask-mobile-platform 📹 Watch

Branch: refactor/940-privacy-screen · Build: E2E · Commit: 4c27ad5 · View full run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant