CVE-2026-0065 AI-generated analysis
platform/frameworks/base, WindowManager / ActivityTaskManager background activity launch (BAL) control
The BAL "bound by foreground UID" exemption in BackgroundLaunchProcessController now requires the binding client UID to have a visible non-pinned (non-PiP) window for activity starts (FGS starts keep the old any-visible-window predicate), and on Android 14/15 additionally requires appSwitchState != APP_SWITCH_DISALLOW unconditionally.
Root cause
BackgroundLaunchProcessController.areBackgroundActivityStartsAllowed() grants BAL_ALLOW_VISIBLE_WINDOW when isBoundByForegroundUid() finds any UID in mBalOptInBoundClientUids (clients bound with BIND_ALLOW_ACTIVITY_STARTS, per the tests) for which mUidHasActiveVisibleWindowPredicate is true. That predicate is ActivityTaskManagerService.hasActiveVisibleWindow(), which treats a UID as foreground if it has any visible non-toast window, including a pinned (picture-in-picture) activity. So a process bound by a client that only had a PiP window was allowed to start background activities. Per the commit message, this was inconsistent with regular BAL behavior, which requires a non-pinned visible window (the Android 15 tests include testForegroundTaskBlockedIfPinned). On Android 14 the bound-by-foreground branch also did not consult appSwitchState. On Android 15 that check was gated behind the flag Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid().
How the fix works
- services/core/java/com/android/server/wm/ActivityTaskManagerService.java: adds hasActiveVisibleNotPinnedWindow(int uid), which returns true if mVisibleActivityProcessTracker.hasVisibleNotPinnedActivity(uid) or mActiveUids.hasNonAppVisibleWindow(uid). It mirrors hasActiveVisibleWindow() but only considers non-pinned app activities.
- services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java: adds the field mUidHasActiveVisibleNotPinnedWindowPredicate and a new constructor parameter uidHasActiveVisibleNotPinnedWindowPredicate. isBoundByForegroundUid() now takes an IntPredicate uidVisibilityPredicate and tests each UID in mBalOptInBoundClientUids against it.
- services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java areBackgroundActivityStartsAllowed(): the bound-by-foreground branch becomes `appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid(isCheckingForFgsStart ? mUidHasActiveVisibleWindowPredicate : mUidHasActiveVisibleNotPinnedWindowPredicate)`. Activity starts now require a non-pinned visible window in the bound client, while FGS-start checks keep the old predicate. On Android 16 the same ternary is applied inside the existing `checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW` condition, using checkConfiguration.isCheckingForFgsStart.
- services/core/java/com/android/server/wm/WindowProcessController.java constructor: passes atm::hasActiveVisibleNotPinnedWindow as the new second argument when constructing BackgroundLaunchProcessController.
- Android 15 only: removes the import of com.android.window.flags.Flags and the flag-gated allowBoundByForegroundUid computation, so the appSwitchState != APP_SWITCH_DISALLOW check becomes unconditional.
- Android 15 and 16 only: services/tests/wmtests/.../BackgroundLaunchProcessControllerTests.java is updated for the new constructor and split into testBoundByForegroundFgs (any visible window) and testBoundByForeground (non-pinned visible window). The Android 14 commit contains no test changes.
Completeness
- Covers only the bound-by-foreground-UID exemption (mBalOptInBoundClientUids path) in BackgroundLaunchProcessController.areBackgroundActivityStartsAllowed(). The process's own visible-task path is not changed by this commit. On Android 15/16 the tests show that path already takes a pinned-window-mode parameter (testForegroundTaskBlockedIfPinned).
- FGS-start checks (isCheckingForFgsStart true) intentionally keep using hasActiveVisibleWindow, so a bound client with only a PiP window still enables FGS starts. The commit message says this is by design.
- hasActiveVisibleNotPinnedWindow still falls back to mActiveUids.hasNonAppVisibleWindow(uid), so for BAL the bound client's non-app visible windows still count as foreground, same as before.
- The non-FGS BAL path now also requires appSwitchState != APP_SWITCH_DISALLOW: this is new on Android 14 and unconditional on Android 15 (it was previously flag-gated).
- Other callers of hasActiveVisibleWindow are not changed by this commit. Only the BAL predicate injected into BackgroundLaunchProcessController is switched.
Behavior changes
- A process bound with BIND_ALLOW_ACTIVITY_STARTS by a UID whose only visible activity is in pinned (PiP) mode no longer receives BAL_ALLOW_VISIBLE_WINDOW for activity starts (BAL_ALLOW_BOUND_BY_FOREGROUND on 16 with balImprovedMetrics). The start falls through to the remaining checks and may be blocked.
- On Android 14, bound-by-foreground BAL is now also denied when appSwitchState == APP_SWITCH_DISALLOW. On Android 15 this behavior is no longer gated by Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid().
- FGS start eligibility via a bound foreground client is unchanged.
- The BackgroundLaunchProcessController constructor signature changes (one extra IntPredicate), which affects any test or caller that constructs it.
Per major
- Android 14: Same fix: RESTRICT AUTOMERGE cherry-pick 407d7b083010 in android-security-14.0.0_r28. It adds the appSwitchState != APP_SWITCH_DISALLOW check as a new condition, since 14 had none. No test changes. 8/8 lines of the patched region present at android-security-14.0.0_r29.
- Android 15: Adapted fix: 3d4a174f1def in android-security-15.0.0_r16. Same predicate split, plus removal of the Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid() gating and the Flags import; this branch returns a BalVerdict. Includes BackgroundLaunchProcessControllerTests updates. 8/8 lines present at android-security-15.0.0_r17.
- Android 16: Adapted fix: 66c209d1f65f in android-security-16.0.0_r7. The ternary is applied inside the existing checkConfiguration.checkVisibility && appSwitchState condition, using checkConfiguration.isCheckingForFgsStart. Test updates add a BalCheckConfiguration for FGS. 8/8 lines present at android-security-16.0.0_r8.
- Android 17: Not affected: the bulletin does not list Android 17. The patched region (8/8 lines) is present at android-security-17.0.0_r1.
Unknowns
- The implementation of VisibleActivityProcessTracker.hasVisibleNotPinnedActivity() is not shown, so the material does not show how it determines pinned state.
- The material does not show which activity-start entry points reach areBackgroundActivityStartsAllowed() with isCheckingForFgsStart false.
- The Android 14 commit includes no test changes. Whether the test file was updated separately on that branch is unknown.
- The Android 14 code of the process's own visible-task / pinned-window check is not shown in the material.