Changing a setting and immediately restarting (e.g. "Restart now" on the
Brave Origin screen) could discard the change; waiting a few seconds avoided
it.
Pref writes are batched behind a timer and flushed to disk later. The Android
restart path SIGKILLs the browser process from a sibling process
(BrowserRestartActivity) with no graceful shutdown, so pending prefs are never
flushed. Whether a change survived depended on an unrelated write happening to
flush the store first, hence the intermittent, single-device reports.
Flush local_state and each loaded profile's prefs via CommitPendingWrite()
before AttemptRestart(). It is async-to-disk, a no-op when nothing is pending,
and the write is atomic.
Resolves: https://github.com/brave/brave-browser/issues/56075
On a fresh profile with a prior Play Store Origin purchase, BraveActivity
auto-restores it at startup via verifyPurchase(). This enforces the Origin
policies (e.g. disabling Brave Wallet) live in the running session, but
unlike the in-settings purchase and the native web-purchase flows it never
prompted a restart. The browser then honours the freshly enforced policy
per navigation while already-configured renderers do not, which can leave
the two out of sync (e.g. the renderer keeps injecting window.ethereum and
requests a binder the browser no longer registers).
Open the Origin settings screen when a Play Store purchase is auto-restored,
reusing the existing fetching-spinner/restart snackbar, so the user restarts
and the policies take effect cleanly at boot. The prompt fires only on the
auto-restore transition (the subscription pref is inactive when verifyPurchase
runs); the in-settings first purchase keeps its own snackbar.
Resolves: https://github.com/brave/brave-browser/issues/56078
On tablet the rewards (BAT) icon lives in the brave_toolbar row, a
wrap_content LinearLayout that caches its measured width. When the icon
is hidden from onMeasure (e.g. when Brave Origin disables rewards), the
implicit requestLayout() from setVisibility() is dropped because the
toolbar is already mid-layout, so the row keeps its old width and the
weighted location bar never reclaims the freed space, leaving a gap
until a device rotation forces a fresh layout.
Route all rewards-slot visibility changes through a helper that, on an
actual change on tablet, re-requests layout on the rewards view itself
(so its row re-measures) off the measure pass, and refreshes the cached
tablet toolbar snapshot.
Resolves: https://github.com/brave/brave-browser/issues/56036
When an Origin purchase is first detected via credential refresh (e.g.
linked from account.brave.com), open the Origin settings screen and show
the existing restart snackbar.
The shared first-purchase trigger in BraveOriginService::OnCredentialSummary
calls the navigation delegate, whose Android branch was previously a no-op.
It now routes through a JNI helper that opens BraveOriginPreferences with an
explicit show-restart flag, where onViewCreated shows the restart snackbar.
Play Store purchases are left to the existing Java subscription flow (which
shows the "Disabling features" spinner while credentials are fetched, then
transitions to the restart prompt). The helper skips opening when a Play
Store purchase token is present so the screen isn't opened twice.
Resolves: https://github.com/brave/brave-browser/issues/55991
Calls BillingClient.showInAppMessages on app foreground so the Play
Store can render its recovery dialog (declined payment, opt-in price
change) on top of Brave. Gated on the user having an active VPN or Leo
subscription, throttled to one call per real foreground session via an
ApplicationStatus listener that resets only on HAS_STOPPED_ACTIVITIES /
HAS_DESTROYED_ACTIVITIES — in-app navigation does not re-trigger.
Resolves: https://github.com/brave/brave-browser/issues/55735
`BraveOriginPolicyManager::Init` was only called lazily from
`BraveOriginServiceFactory::BuildServiceInstanceForBrowserContext`,
which runs during profile keyed-service build. Profile pref-store init
waits on `PolicyService::IsInitializationComplete`, which waits on
`BraveProfilePolicyProvider::IsInitializationComplete`, which gates on
`manager->IsInitialized()` -- and that's only flipped by the lazy call
that itself is gated on profile build. Cycle manifests as "stuck on
profile selection screen" for some users.
Initialize the manager eagerly at the end of `BraveBrowserProcessImpl::Init()`,
right next to the existing `AdBlockOnlyModePolicyManager::Init` call. The
factory's lazy `if (!IsInitialized()) Init(...)` block stays as a backstop
for tests that bypass `BraveBrowserProcessImpl`.
Resolves: https://github.com/brave/brave-browser/issues/55733
`BraveStatsUpdater` is a browser-process singleton; its 3-second startup
timer reads `kStatsReportingEnabled` without synchronising against
`BraveOriginPolicyManager`. Make the updater observe the manager
directly and defer the timer behind a two-phase gate (referrals barrier
+ `OnBravePoliciesReady`), so the startup ping cannot fire before the
policy bundle is merged into local state.
Resolves: https://github.com/brave/brave-browser/issues/55811
`BraveNewsController` is built eagerly with the profile and its ctor
immediately calls `ConditionallyStartOrStopTimer()`. The gate reads
`kBraveNewsDisabledByPolicy` before the policy bundle has been merged
into the managed pref store, so the managed value isn't visible yet and
News issues its usual feed / publisher fetches even when Brave Origin's
`BraveNewsDisabled` policy is set.
Defer the initial `ConditionallyStartOrStopTimer()` call behind a
`brave_policy::PolicyInitializationWaiter`, and tighten
`brave_news::IsEnabled()` to require `IsManagedPreference`.
Resolves: https://github.com/brave/brave-browser/issues/55759
* [Origin] Honor BraveRewardsDisabled policy when starting Rewards engine
Defer `RewardsServiceImpl::CheckPreferences()` -- the gate that decides
whether to spin up the Rewards engine process -- behind a
`brave_policy::PolicyInitializationWaiter` so that the managed
`brave_rewards::prefs::kDisabledByPolicy` pref is visible by the time the
gate evaluates. Without this, `Init()` runs before the policy bundle has
been merged into the managed pref store and the engine starts -- pinging
`/v1/parameters` and `/v4/wallets/...` -- even when Brave Origin has
disabled Rewards via `BraveRewardsDisabled`.
Resolves: https://github.com/brave/brave-browser/issues/55696
Move BraveConstants.java from org.chromium.chrome.browser.util to
org.chromium.chrome.browser to align with upstream's hierarchy.
Upstream's analogous ChromeStringConstants lives directly under
org.chromium.chrome.browser, and the util package is treated as
deprecated for new top-level constants/utilities.
No behavior changes — pure refactor.
Resolves: https://github.com/brave/brave-browser/issues/55685
Adds support for the https://brave.com/deeplink-android-origin-promo
deep link so that Google Play promotional content events for Brave
Origin can route users directly into the Origin payment screen (or
settings page for existing subscribers).
Routing happens via the existing autoVerified brave.com App Link.
BraveActivity catches the intent before upstream's URL handling in
initializeState (cold start) and onNewIntent (warm start), and the
Play Install Referrer flow continues to support deferred routing for
first-install attribution.
Resolves: https://github.com/brave/brave-browser/issues/55654
[Origin] Gate BraveProfilePolicyProvider on BraveOriginPolicyManager init
`BraveProvider`'s first `RefreshPolicies` is triggered by
`AdBlockOnlyModePolicyManager` (initialised at process start), well before
`BraveOriginPolicyManager` (lazily initialised by `BraveOriginServiceFactory`)
is ready. The resulting bundle lacks `BraveRewardsDisabled`, but the
default `IsInitializationComplete` lets `PolicyService` report ready
anyway. `AdsServiceImpl::MaybeStartBatAdsService` then evaluates a stale
managed pref store and bat-ads starts despite the policy.
Override `IsInitializationComplete` to additionally gate on
`BraveOriginPolicyManager::IsInitialized()`. Upstream unit tests
(`ProfilePolicyConnectorTest`) bypass our factory bootstrap, so add a
`SetExpectedToBeInitialized` flag set from `BraveOriginServiceFactory`'s
ctor; the override short-circuits when unset (desktop/Android only --
iOS doesn't run those tests).
Resolves: https://github.com/brave/brave-browser/issues/54962
* Decouple PolicyService::Observer from AdsServiceImpl
Move the "defer ads service start until policy is initialized" pattern out
of `AdsServiceImpl` into a reusable `brave_policy::PolicyInitializationWaiter`
helper. `AdsServiceImpl` no longer inherits from `policy::PolicyService::Observer`
or takes a `policy::PolicyService*` in its constructor; `AdsServiceFactory`
builds the waiter from the profile policy connector and injects it.
Also drops the temporary `[AdsGate]` verbose logging that was added with the
original race fix; behaviour is now covered by unit tests.
The new helper lives at `brave/components/brave_policy/policy_initialization_waiter.{h,cc}`
and is reusable by any service that needs to defer initialization until
`PolicyService` has merged its policy bundle into the managed pref store.
Resolves: https://github.com/brave/brave-browser/issues/55266
Aligns brave:// with chrome:// in IntentHandler.intentHasUnsafeInternalScheme.
The upstream guard is unaware of the Brave alias because the brave:// →
chrome:// rewrite happens after the intent layer.
BraveIntentHandlerClassAdapter redirects the upstream call site through
BraveIntentHandler.intentHasUnsafeInternalScheme. The Brave method defers to
upstream first, then additionally rejects brave:// under the same category
guard upstream uses. A new BraveIntentHandlerInternal helper holds the
bytecode stub used to back-call the (private) upstream method.
Resolves: https://github.com/brave/brave-browser/issues/55473
* [Ads][Origin] Defer ads service start until policy service is initialized
The ads gate reads `kDisabledByPolicy` via `IsManagedPreference`, so if
`AdsServiceImpl` is constructed before the BraveOrigin policy bundle has
been merged into the managed pref store, the gate evaluates as unmanaged
and the service starts. NTT ads can then serve despite
`BraveRewardsDisabled` being active.
Defer the initial `MaybeStartBatAdsService()` call until
`PolicyService::IsInitializationComplete(POLICY_DOMAIN_CHROME)` is true.
Adds `[AdsGate]` diagnostics behind the existing "Verbose Logs for
Ad-Rewards" QA toggle.
Resolves: https://github.com/brave/brave-browser/issues/55228
The policy YAMLs for BraveVPNDisabled, BraveAIChatEnabled and
BravePlaylistEnabled listed Android only under future_on:, never under
supported_on:. Codegen marks them is_future on Android, and
ConfigurationPolicyHandlerList::IsBlockedFuturePolicy drops is_future
policies from the bundle on Stable and Beta channels — so the matching
managed prefs (kManagedBraveVPNDisabled, ai_chat::prefs::kEnabledByPolicy,
playlist::kPlaylistEnabledPref) never become managed, and the Java hide
checks in BraveMainPreferencesBase.onResume() and the cross-platform
playlist::IsPlaylistAllowed() never see the policy. Nightly and local
debug builds (channel != Stable/Beta) bypass the gate, which is why this
escaped pre-release.
Move android from future_on: to supported_on: android:148- in the three
YAMLs, matching the existing pattern used for BraveWalletDisabled and
BraveNewsDisabled.
Add OriginPolicies_NotFutureOnThisPlatform unit test that walks every
entry in BraveOriginServiceFactory::GetBrowserPolicyDefinitions() and
GetProfilePolicyDefinitions() and asserts policy_details.is_future is
false. The test runs on every channel and target, so future regressions
of the same shape — Brave Origin claiming to enforce a policy that the
runtime gate will silently drop — surface in unit tests instead of in
released Stable/Beta builds.
Resolves: https://github.com/brave/brave-browser/issues/55346
Hides the "Show New Tab Page Ads" toggle and the
"Learn more about new tab page ads" link in
Settings -> New Tab Page when Brave Rewards is disabled by policy.
Both controls remain visible and functional otherwise.
Also switches BackgroundImagesPreferences to retrieve the profile via
the inherited getProfile() instead of the legacy
ProfileManager.getLastUsedRegularProfile().
Resolves: https://github.com/brave/brave-browser/issues/55312
* [Origin] Wait for all policy managers before refreshing profile policies
`BraveProfilePolicyProvider` observes both `BraveOriginPolicyManager`
and `AdBlockOnlyModePolicyManager`. Each fires `OnBravePoliciesReady`
independently, so the first notification could call `RefreshPolicies`
before the other manager's `Init()` had run -- producing a bundle that
reflected only one source and flipping `IsFirstPolicyLoadComplete` true
on the empty load.
Introduce `BravePolicyManagerBase` with self-registration and a static
`AllInitialized()`. `OnBravePoliciesReady` early-returns until every
registered manager reports ready; `policies_ready_` is gated on the
same check so `SetProfileID` can't trigger a partial refresh either.
Future managers inheriting from the base are picked up automatically.
Resolves: https://github.com/brave/brave-browser/issues/55276
After switching the menu button visibility from INVISIBLE to GONE in
https://github.com/brave/brave-core/pull/35051, the shred button became
the rightmost child of menu_button_container and lost the spacing the
menu button used to contribute via its paddingEnd. Apply an end margin
equal to the toolbar's start inset when the menu button is hidden so
the icon stays clear of the screen edge and the toolbar reads
symmetrically.
Resolves: https://github.com/brave/brave-browser/issues/55012
BraveOrigin profile policies (e.g. BraveRewardsDisabled) were not landing
in the managed pref store before AdsService's CanStartBatAdsService gate
ran on Android and desktop. The race had two parts:
1. BraveOriginPolicyManager was initialized lazily from the per-profile
keyed-service factory, which on Android's startup-data path runs after
the profile policy connector is already built.
2. BraveProfilePolicyProvider::SetProfileID was called late (from
BRAVE_PROFILE_IMPL_TAKE_PREFS_FROM_STARTUP_DATA), so OnBravePoliciesReady
couldn't trigger RefreshPolicies until well after PolicyServiceImpl had
been constructed with our empty bundle.
Two changes shift the policy push earlier in startup so the async merge
posted by PolicyServiceImpl::OnUpdatePolicy has time to propagate before
consumers read the pref store:
- BraveBrowserProcessImpl::Init eagerly initializes BraveOriginPolicyManager
with local_state().
- BraveProfilePolicyProvider gets a thread-local profile-path stash that
is populated by callers immediately before profile policy connector
creation: chromium_src wrappers for both
CreateProfilePolicyConnectorForBrowserContext (desktop) and
StartupData::CreateServicesInternal (Android, via two new macro hooks
in upstream chrome/browser/startup_data.cc). The
BRAVE_PROFILE_POLICY_CONNECTOR_INIT macro consumes the stash and calls
SetProfileID before provider->Init() runs, so OnBravePoliciesReady
fires synchronously inside Observe() with profile_id_ already set and
triggers RefreshPolicies in time.
Also adds an early-return in SetProfileID so legacy
SetBraveProfilePolicyProviderProfileID call sites don't re-fire
RefreshPolicies for the already-set id.
Resolves: https://github.com/brave/brave-browser/issues/54991
* [Android] Migrate private NTP to Leo tokens and fix VPN icon visibility
- Drop Brave-local duplicates of ic_product_vpn / ic_antenna /
ic_product_private_window so the private NTP renders the Leo
(Nala) icons via resource_overlay; tint the inner icon layer to
primitive_private_window_60 in IncognitoNewTabPageView so the
designed purple still wins on the fixed-dark surface.
- Remove Brave-local primitive_private_window_5/10/20/60/80/90 from
brave_colors.xml; references now resolve through Leo's primitive
scale (same hex for _60/80/90; slight neutral shift for _5/10/20).
- Remove the runtime setBackgroundColor on the scroll view that was
masking the gradient drawable defined in the layout XML.
- Switch the private-tab logo to ic_social_brave_release_favicon_-
fullheight_color (Leo) and size in dp (42 phone, 56 tablet) to
match Figma and to stop scaling with accessibility font size.
- Resize circle drawable to 32x32 (was 32x33), constrain inner icon
via new_tab_page_incognito_icon_inner_size (20dp / 24dp tablet),
and grow circle to 48dp on tablets.
- Introduce per-form-factor style aliases (IncognitoNtpTitle /
SectionTitle / SectionDescription / Cta) under values/ +
values-sw600dp/ so phones and tablets land on the right Leo
typography ramp without per-TextView attributes.
- Delete the now-dead ntp_bg_incognito and incognito_modern_-
primary_color color entries.
Resolves: https://github.com/brave/brave-browser/issues/53958
The Survey Panelist toggle and its "Learn more" row remained visible
under Settings -> Brave Shields & privacy for users with an active
Brave Origin subscription.
Extend the existing BRAVE_NTP_BRANDED_WALLPAPER_SURVEY_PANELIST
feature-flag gate in BravePrivacySettings to also require
!BraveOriginSubscriptionPrefs.getIsCredentialSummaryActiveCached() for
both the preference visibility and the settings-search index entry.
Resolves: https://github.com/brave/brave-browser/issues/54853
brave_rewards::features::kBraveRewards is DISABLED_BY_DEFAULT on x86/x86_64
Android official builds, so brave_rewards::IsSupported returned false via
IsDisabledByFeature. After https://github.com/brave/brave-core/pull/35698
moved the IsSupported gate into AdsServiceImpl::CanStartBatAdsService, that
path now suppresses the service in unit tests too and launch_count stays 0.
Force-enable the feature in the fixture so Android tests match the
non-Android path they were written against.
Resolves: https://github.com/brave/brave-browser/issues/54831
Brave Origin subscribers on Android currently receive general-engagement
notifications and unbidden promo dialogs that are not tied to any
Origin-disabled feature. Suppress them under the product stance that
Origin users get a cleaner, promo-free experience.
Surfaces gated:
- RetentionNotificationPublisher: HOUR_3, HOUR_24, DAY_6, EVERY_SUNDAY,
and DORMANT_USERS_DAY_14/25/40 notification fire-time.
- BraveActivity: showDormantUsersEngagementDialog (cold-start and
in-foreground tap paths), India ad-free callout, YouTube-in-Brave
dialog.
The existing getIsSubscriptionActive() pref only reflects Play Store
purchases, so it misses desktop-linked subscribers. Introduce a new
Java-side cached boolean BRAVE_ORIGIN_CREDENTIAL_SUMMARY_CACHED, written
from two authoritative any-source signals:
- fetchOrderCredentials success (primes the cache immediately on a
successful Play Store purchase).
- requestCredentialSummary callback (covers Play Store and
desktop-linked paths; the Skus service resolves both internally).
BraveActivity.finishNativeInitialization now calls
requestCredentialSummary on every app start to keep the cache fresh.
Resolves: https://github.com/brave/brave-browser/issues/54791
BraveActivity shows LinkVpnSubscriptionDialogFragment when a VPN
subscription is detected on the device and the link dialog has not yet
been shown. The guard did not consult the VPN policy pref, so the dialog
was presented even to users whose VPN has been disabled by policy (e.g.
Brave Origin users with VPN turned off at the policy layer).
Add !BraveVpnPolicy.isDisabledByPolicy(mTabModelProfileSupplier.get()) to
the guard.
Resolves: https://github.com/brave/brave-browser/issues/54786
String changes in android_brave_strings.grd:
- IDS_MENU_ORIGIN: "Origin" -> "Brave Origin" (Settings menu row).
- IDS_BRAVE_ORIGIN_PREMIUM: "Origin" -> "Brave Origin" (paywall title).
- IDS_ORIGIN_LEO_AI_TOGGLE: "AI" -> "Leo AI" (toggle label
consistency with other Leo surfaces).
- IDS_ORIGIN_DESCRIPTION_BODY: drop the trailing sentence
"Origin will remove most other features from the browser."
- IDS_ORIGIN_CHANGING_BRAVE_FEATURES_MESSAGE_POST_PURCHASE:
"once Origin has finished updating." ->
"once Brave Origin has finished updating."
- IDS_ORIGIN_PAYWALL_TEXT_4: rewritten to
"One-time purchase can be activated multiple times across all
your devices".
- IDS_ORIGIN_DESCRIPTION_NOTE: removed entirely (Search Premium
note no longer shown).
Layout changes:
- brave_origin_description_preference.xml: remove the
origin_description_note TextView from the preference-screen
description block.
- activity_brave_origin_plans.xml (portrait paywall) and
layout-land/activity_brave_origin_plans.xml (landscape
paywall): remove the note TextView; body description's bottom
margin bumped from 16dp to 24dp to preserve spacing with the
next section.
Resolves: https://github.com/brave/brave-browser/issues/54761
BraveSearchEnginesPreferences.removePreferenceIfPresent was calling
getPreferenceScreen().removePreference(pref) unconditionally. That
only removes direct children of PreferenceScreen — when the target
preference is nested inside a PreferenceCategory (as send_web_discovery
is, inside currently_used_search_engines_category), the remove call
silently returns false and the toggle stays visible.
Users whose WebDiscoveryEnabled pref is managed by policy (e.g. Brave
Origin subscribers, enterprise admins) saw the toggle rendered and
greyed-out instead of being hidden entirely.
Fix: remove from the preference's actual parent group.
preference.getParent() returns the containing PreferenceGroup — the
PreferenceCategory for nested prefs, the PreferenceScreen itself for
top-level prefs. Works for both cases.
Other PREF_CUSTOM_SEARCH_ENGINES_CATEGORY caller is a top-level
PreferenceCategory; new behavior is equivalent for that case.
Resolves: https://github.com/brave/brave-browser/issues/54751
brave_rewards::IsDisabledByPolicy() was hardcoded to return false on
Android regardless of the managed kDisabledByPolicy pref, so every
native call site that gates on brave_rewards::IsSupported() failed to
suppress for Brave Origin subscribers (and enterprise admins setting
the BraveRewardsDisabled policy).
Symptoms fixed:
- Ads service kept initializing on Android for Origin profiles
(AdsServiceFactory::GetForProfile gates on IsSupported).
- NTP sponsored background images and sponsored rich-media takeover
rendered (downstream of the ads service via ViewCounterService).
- DAY_10 / DAY_30 / DAY_35 Rewards retention notifications fired
(RetentionNotificationPublisher gates on rewardsNativeWorker
.isSupported).
Java-side Rewards UI surfaces were already correctly gated via
BraveRewardsPolicy.isDisabledByPolicy(profile) and are unaffected.
Also decouples BraveVpnUtils.isRegionSupported() from
BraveRewardsNativeWorker.isSupported() — otherwise the guard flip would
cause VPN UI to hide for any profile where Rewards is policy-disabled
as a side effect. Added BraveVpnNativeWorker.isSupportedRegion that
does the OFAC region check directly via brave_l10n.
Resolves: https://github.com/brave/brave-browser/issues/54703
* [Android] Fix Leo file upload using display_name for type detection
On Android, SelectFileDialog returns info.path() as a content:// URI
without a file extension, so DetermineFileType() cannot classify the
file and uploads silently fail. Use info.display_name, which carries
the real filename with extension, for the Android path.
Resolves: https://github.com/brave/brave-browser/issues/54555
* Fix android unit test
When a user who previously purchased Brave Origin switches devices,
verifyPurchase() at startup finds the Play Store purchase and kicks off
the SKUs createOrderFromReceipt / fetchOrderCredentials chain to obtain
an order ID. Until that chain completes, requestCredentialSummary
returns inactive, so Settings -> Brave Origin launches the paywall even
though a purchase exists and is mid-restore.
Gate handleOriginPreferenceClick on isFetchingCredentials() and route
directly to BraveOriginPreferences (which renders the fetching spinner
via its existing setCredentialsFetchedCallback path) when a restore is
in flight.
Also harden setCredentialsFetchedCallback against a lost-notification
race: after registering the callback, re-check isFetchingCredentials()
and synthesize a success notification if the fetch already completed.
Order-ID-set is the only path that flips the state to false, so
synthesizing success is safe.
Resolves: https://github.com/brave/brave-browser/issues/54637
* [Android] Register Brave as Android AutofillService for address autofill
Re-enables the Android AutofillService that was removed in
https://github.com/brave/brave-core/pull/25759 due to crashes on
split-APK installs. Uses the SplitCompat pattern
(SplitCompatAutofillService base class) so the service works correctly
when installed from the Play Store.
When the user selects Brave as their autofill provider in Android Settings,
Brave can fill saved address profiles into third-party apps and save new
addresses from manually filled forms.
Resolves: https://github.com/brave/brave-browser/issues/41334
The deferred PostTask introduced to avoid ANR was causing the bottom
controls to be initialized after the progress bar, breaking it.
Revert to synchronous initialization.
Resolves: https://github.com/brave/brave-browser/issues/54458
The upstream Chromium change
https://chromium-review.googlesource.com/c/chromium/src/+/7567418
("Clean up LocationBar focus and UrlBar management") in cr147 added a
setUrlBarFocus() call in SearchActivity.beginQuery(). This triggers the
omnibox suggestion pipeline (serveCachedZeroSuggest ->
buildDropdownViewInfoList) before native libraries are loaded.
BraveDropdownItemViewInfoListBuilder.isBraveLeoEnabled() and
isBraveSearchPromoBanner() both call ChromeFeatureList.isEnabled() which
requires native, causing UnsatisfiedLinkError on release builds and
AssertionError on debug builds.
Fix: reorder the && conditions to check tab != null before any native
calls. Since there's no tab in SearchActivity, the cheap null check
short-circuits and avoids the native call entirely. No functional
change - these features were never shown in the search widget context.
Resolves: https://github.com/brave/brave-browser/issues/54444
The maybeHideRewardsLayout() method controls rewards icon visibility on
tablets based on width but didn't check BraveRewardsPolicy, causing it
to override the main visibility logic that correctly hides the icon when
rewards are disabled.
Resolves: https://github.com/brave/brave-browser/issues/54358
Always check credentials via SKUs SDK (requestCredentialSummary) in
handleOriginPreferenceClick() instead of gating on the Play Store
purchase pref. This mirrors how Leo uses getPremiumStatus() to handle
both Play Store and desktop purchases.
Only show "Link Purchase" when there is an actual Play Store purchase
that hasn't been linked yet. Desktop purchases have no Play Store
receipt to link.
Resolves: https://github.com/brave/brave-browser/issues/54312
Upstream Chromium cr147 (https://chromium-review.googlesource.com/c/chromium/src/+/7559078)
made FOREGROUND_SERVICE_CAMERA unconditional for all Android builds by
removing the is_desktop_android guard. However, the feature that
actually uses it (kAndroidEnableBackgroundMediaCapturing) is disabled
by default and not enabled by Brave, so the permission is declared but
never used.
This blocks Play Store publishing because Google requires a declaration
with a demo video for any app using FOREGROUND_SERVICE_CAMERA.
This PR:
- Re-gates FOREGROUND_SERVICE_CAMERA behind is_desktop_android so it's
excluded from Brave builds
- Removes AndroidManifest_user_permissions.xml since upstream now
declares FOREGROUND_SERVICE_MICROPHONE unconditionally, making
Brave's duplicate unnecessary
Resolves: https://github.com/brave/brave-browser/issues/54143
Move the bottom controls setup (ViewStub inflation, coordinator
creation, native initialization) from synchronous execution during
finishNativeInitialization to a deferred UI task via PostTask. The
synchronous inflation was triggering HardwareRenderer.nNotifyExpensiveFrame
which blocked the main thread waiting for the GPU, causing ANR on
devices under GPU pressure during startup.
This is safe because all consumers of mBottomControlsCoordinatorSupplier
and mTabGroupUiOneshotSupplier already null-check before use, and the
bottom toolbar starts with visibility="gone" making the one-frame delay
imperceptible. The task is wrapped with mCallbackController.makeCancelable()
to auto-cancel during Activity teardown.
Resolves: https://github.com/brave/brave-browser/issues/54077
* [Android] Move tab switcher controls to bottom when address bar is at bottom
When the address bar is configured to show at the bottom, reposition the
entire hub toolbar (new tab, menu, pane switcher, search, shred) from
the top to the bottom of the tab switcher for one-handed accessibility.
- BraveHubManagerImpl: Add maybeRepositionToolbarToBottom() that changes
the toolbar wrapper's FrameLayout gravity to BOTTOM and swaps the pane
host container's top margin to bottom margin. Uses EdgeToEdge pad
adjuster (same pattern as upstream HubBottomToolbarCoordinator) to
handle navigation bar insets. Properly cleans up the adjuster on hub
hide since the coordinator and views are destroyed between show/hide
cycles.
- BraveHubManagerImplClassAdapter: Expose parent's mEdgeToEdgeSupplier
field via deleteField/makeProtectedField bytecode pattern.
- BraveToolbarManager: Remove setLayoutStateProvider() and its
LayoutStateObserver that toggled menu popup direction on tab switcher
transitions. No longer needed since the hub toolbar is now at the
bottom when address bar is at bottom.
- BraveHubToolbarView: Change INVISIBLE to GONE for hidden action/menu
buttons when bottom navigation controls are enabled (address bar on
top). INVISIBLE left the buttons clickable despite being hidden.
Resolves: https://github.com/brave/brave-browser/issues/53326
* [Android] Fix autofill toggle greyed out when Google is the system autofill service
When Google's Autofill with Google (AWG) is set as the preferred autofill
service in Android OS settings, Chromium disables the third-party autofill
toggle in Settings because Chrome has its own built-in Google-synced
autofill. Since Brave does not have built-in Google autofill, AWG should
be treated like any other third-party provider (e.g. 1Password, Bitwarden).
This fix has two parts:
Java side: Override AutofillClientProviderUtils.getAndroidAutofillFramework-
Availability() via bytecode changeMethodOwner to remap the
ANDROID_AUTOFILL_SERVICE_IS_GOOGLE status to AVAILABLE or SETTING_TURNED_OFF
based on the user's existing pref. This makes the toggle interactive in the
Settings UI.
Native side: The C++ code calls the Java method via JNI, bypassing the
bytecode redirect. To handle this, BraveAutofillClientProviderUtils is
annotated with @CalledByNative to generate its own JNI header. A chromium_src
override of autofill_client_provider.cc uses #define to redirect the original
JNI function to the Brave version, ensuring the native code also uses the
remapped availability status and doesn't reset the pref on browser startup.
Resolves: https://github.com/brave/brave-browser/issues/52960
Remove policy-disabled features from the settings search index so
they don't appear in search results when disabled by Brave Origin
or enterprise policy.
Resolves: https://github.com/brave/brave-browser/issues/53530
* [Android] Show loading state in Origin preferences during post-purchase credential fetch
When a Brave Origin purchase is completed, there is a delay while the
receipt is passed from Play Store to the SKUs SDK via
fetchOrderCredentials. During this time, the Origin preferences screen
opens but credentials aren't ready yet.
- Disable all toggles and clickable preferences until
fetchOrderCredentials completes
- Show a snackbar with "Disabling features" spinner instead of
"Restart now" during the credential fetch
- On success, re-enable preferences and transition to "Restart now"
- On failure, close the preferences screen
- Always show restart snackbar on toggle change regardless of
setPolicyValue result, since failure means a restart is needed
- Remove duplicate color definitions (schemes_primary_fixed_dim,
schemes_on_primary_fixed, semantic_text_secondary,
semantic_text_tertiary) that already exist in nala tokens
Resolves: https://github.com/brave/brave-browser/issues/53915
Add support for linking Brave Origin subscriptions after purchase on
Android, following the same link-order flow used by Leo (AI Chat).
- Add OriginIAPSubscription Mojo interface and C++ implementation to
read purchase token/order ID from prefs and persist link status
- Handle product=origin in SubscriptionRenderFrameObserver: inject
braveOrigin.receipt and braveOrigin.orderId into localStorage on
the initial landing page, and linkResult.setStatus() callback on
the /order-link/ result page
- Register OriginIAPSubscription Mojo binding in
BraveContentBrowserClient
- Update LinkSubscriptionUtils to use link-order intent for Origin
- Hide Purchase section in Origin preferences when already linked
- Move subscription_render_frame_observer.h include out of
ENABLE_BRAVE_VPN guard so it is available for all Android
subscription products
- Generalize Leo-specific constants (kIntentParamValueLeo,
kResultLandingPagePathLeo) since they are now shared with Origin
- Add Origin test cases to browser tests
Resolves: https://github.com/brave/brave-browser/issues/53613
BraveRewardsNativeWorker crashes when constructed before the browser is
fully initialized. This happens when an AlarmManager retention
notification (DAY_10/DAY_30/DAY_35) fires and the app process is started
by Android just to handle the broadcast — ProfileManager::
GetActiveUserProfile() dereferences a null g_browser_process in this
case.
The fix:
- Checks that GetActiveUserProfile() returns a valid profile before
creating the native worker in JNI_BraveRewardsNativeWorker_Init. If it
doesn't, the native object is not created.
- Resets the Java singleton when native init is skipped, so subsequent
calls can retry once the browser is fully initialized.
- Adds null-profile guards to IsSupported, IsSupportedSkipRegionCheck,
and IsRewardsEnabled which also call GetActiveUserProfile()
independently.
- Adds null checks for getInstance() at all call sites since it can now
return null.
Resolves: https://github.com/brave/brave-browser/issues/53877
Convert nala from copy-only targets to a proper android_resources
target so modules with their own resources_package can reference
nala icons via R.drawable at compile time. Use the nala
social-brave-release-favicon-fullheight-color icon for Brave Search
in both the Quick Search Engines view and settings adapters.
Resolves: https://github.com/brave/brave-browser/issues/51364
When browsing history is cleared, TemplateUrlService removes
auto-discovered OpenSearch engines, but the QSE SharedPreferences
cache retained stale entries indefinitely. Add a pruning step in
updateSearchEngines() that removes cached entries whose keywords
no longer exist in TemplateUrlService.
Resolves: https://github.com/brave/brave-browser/issues/43104
Guard mojo calls with try/catch for IllegalStateException since the
pipe can be closed by onDetachedFromWindow() or onConnectionError()
before the BEST_EFFORT task executes, and Java mojo bindings have no
non-destructive validity check.
Resolves: https://github.com/brave/brave-browser/issues/53479
When a user purchases Brave Origin on one device and later switches to a
new device (or reinstalls), the local preferences are empty and the
Origin menu always shows the purchase screen instead of the settings
screen.
This adds a verifyPurchase() method to BraveOriginSubscriptionPrefs that
queries Google Play for an existing Origin purchase and restores the
local preferences (purchase token, product ID, package name) if one is
found. The query is triggered during
BraveActivity.finishNativeInitialization() only when the local
subscription pref is not already active, so it doesn't run unnecessarily
for users who already have their purchase recorded locally.
Toasts are suppressed during the startup query to avoid showing billing
error messages on devices without Google Play, and re-enabled when the
purchase screen is opened.
Resolves: https://github.com/brave/brave-browser/issues/53405
The verified publisher checkmark could persist when switching tabs
because brave:// URLs were not treated as internal URLs and
OnPanelPublisherInfo silently dropped null publisher info without
notifying the Java layer.
- Treat brave:// URLs as internal alongside chrome:// so they
clear the checkmark instead of querying getPublisherInfo.
- Notify Java with an empty publisher ID when OnPanelPublisherInfo
receives null info, so the UI clears stale state.
- Reset the checkmark immediately in didSelectTab as a safety net
before the async publisher query completes.
Resolves: https://github.com/brave/brave-browser/issues/53332
Migrate Origin settings icons from custom drawables with hardcoded fill
colors to Leo design token equivalents that use @color/icon_default for
proper dark theme support. Remove manual icon tinting code that was
working around the hardcoded colors.
Resolves: https://github.com/brave/brave-browser/issues/53334
The email aliases toggle in Brave Origin settings was always visible
regardless of whether the feature flag is enabled. Since the feature is
disabled by default, users would see a non-functional toggle.
Expose kEmailAliases to Java via ChromeFeatureList and hide the
preference when the flag is disabled.
Resolves: https://github.com/brave/brave-browser/issues/53323
fix: DayZeroBrowserUIExptManager crashes when P3A is toggled off-on-off
ResetBrowserUIStateForAllProfiles() was resetting observation_ when P3A
was disabled, but SetDayZeroBrowserUIForAllProfiles() never re-established
it when P3A was re-enabled. Toggling P3A off a second time hit the
CHECK(observation_.IsObserving()) because the observation was already reset.
Remove the observation_.Reset() from ResetBrowserUIStateForAllProfiles().
It's unnecessary because OnProfileAdded() already guards on IsP3AEnabled(),
making it a no-op when P3A is off.
Also destroy the p3a_enabled_ pref subscription in
OnProfileManagerDestroying() to prevent the callback from firing after
the ProfileManager is gone.
Resolves: https://github.com/brave/brave-browser/issues/53294
Chromium 146 introduced BadMessageException in the Java Mojo bindings
(crbug.com/469861566, https://github.com/chromium/chromium/commit/c04b8552deeef)
to surface malformed or unexpected messages (invalid headers, unknown methods,
deserialization errors, responses for already-cancelled requests). However, the
default ExceptionHandler simply re-throws the exception as unchecked, causing
crashes instead of gracefully closing the pipe.
This differs from the C++ Mojo bindings behavior where
Connector::DispatchMessage (mojo/public/cpp/bindings/lib/connector.cc) handles
dispatch failures by calling HandleError() which resets the pipe cleanly.
Set a delegate on ExceptionHandler.DefaultExceptionHandler that catches
BadMessageException and returns false (closing the pipe), while re-throwing
all other exceptions to preserve existing behavior.
Resolves: https://github.com/brave/brave-browser/issues/53290
PlayCoreDialogWrapperActivity (from core-common) lives in base.apk
but deserializes a Parcelable (review.zzc) from split_chrome.apk.
The base split's ClassLoader can't see chrome split classes, causing
ClassNotFoundException. Chromium fixes this for its own activities
via BundleUtils.checkContextClassLoader(), but PlayCoreDialogWrapperActivity
extends plain Activity and is not covered.
Fix by registering an ActivityLifecycleCallbacks that sets the
Application's ClassLoader on the Intent extras Bundle before onCreate().
Also adds a QA preference to force the in-app review flow from settings
for easier testing.
Resolves: https://github.com/brave/brave-browser/issues/53179
Override DownloadLocationDialogCoordinator to force the download
location dialog even on single-directory devices (no SD card).
Upstream skips the dialog when only one storage directory is
available; the Brave override intercepts that skip and re-triggers
the dialog display. Also transitions SHOW_INITIAL to SHOW_PREFERENCE
so the "Don't show again" checkbox starts unchecked, preventing the
feature from silently disabling itself after the first use.
Resolves: https://github.com/brave/brave-browser/issues/52737
* [Android] Fix NPE in PendingTxHelper when TxService is null
Add null guard for mTxService in fetchTransactions() and null it out
in destroy(). The observer callback can fire after the service is gone
(e.g., wallet service shutdown), causing an NPE at getAllTransactionInfo.
Rename non-public non-static fields to follow the m-prefix convention
required by Chromium's Java style. No functional changes.
Resolves: https://github.com/brave/brave-browser/issues/53102
Pass a temporary no-op HomeSurfaceTracker when mHomeSurfaceTracker is
null during warm startup to prevent the NPE in upstream's
setInitialOverviewStateOnResumeWithNtp. This can happen on activity
recreate (rotation, process death, foldable transitions) where
onResumeWithNative fires before the tracker is initialized.
Resolves: https://github.com/brave/brave-browser/issues/53071
Brave Origin on Android was scaffolded as a subscription (monthly/yearly)
but never shipped. This switches it to a one-time perpetual purchase
using BillingClient.ProductType.INAPP instead of SUBS.
Changes:
- Replace Origin subscription product IDs (monthly/yearly) with perpetual
IDs: brave.origin.perpetual, beta.origin.perpetual,
nightly.origin.perpetual
- Add dedicated INAPP query (queryOriginProductDetailsAsync), purchase
initiation (initiatePurchaseInApp), and price formatting methods for
one-time purchases which don't use offer tokens or subscription phases
- Update queryPurchases to use ProductType.INAPP when querying Origin
- Wire up BraveOriginPlansActivity billing flow: product details loading,
price display, and Buy Now button
- Add "Consume Origin Purchase" option in QA Developer Preferences to
allow testers to clear the Play Store entitlement for re-testing
- Clean up BraveQAPreferences: migrate from android.app.AlertDialog to
androidx.appcompat.app.AlertDialog and from SharedPreferences to
ChromeSharedPreferences
Resolves: https://github.com/brave/brave-browser/issues/53006
Update Android Origin paywall from subscription to one-time purchase
- Replace monthly/yearly subscription plan selectors with single "One Time Purchase" plan row
- Add 4th feature benefit: one-time purchase, no subscription cost
- Add Origin description and Brave Search Premium note sections
- Replace "Try 7 days free" with "Buy now" button
- Replace "Refresh your credentials" with "Get a login code" button
- Remove "Learn more about Origin" button and unused resources
- Update text styles per design spec
- Comment out billing integration for later hookup
Resolves: https://github.com/brave/brave-browser/issues/52855
Redesign the Origin preferences screen: add a custom restart
snackbar with dismiss button shown on toggle changes, split then
description into styled title/body/note sections, and rename
the bottom section from Subscription to Purchase.
Resolves: https://github.com/brave/brave-browser/issues/52774
[Android] Fix for NTP idle snackbar shows when opening external links
from other apps. When the NTP idle experiment (variants B/C) is active
and the inactivity threshold has been reached, opening a link from an
external app should not show an NTP+snackbar.
Resolves: https://github.com/brave/brave-browser/issues/52746
[Android] Fixes a crash in FaviconUtils.getIconDrawableWithFilter
when an icon is not available for a particular URL by falling back
to the default globe icon.
Resolves: https://github.com/brave/brave-browser/issues/52458
Currently there is an async mojo call to check policies for Leo and
Rewards, that is an overhead and not needed, in fact it could be wrong
if the policies controlled by something else that is not Origin.
Resolves: https://github.com/brave/brave-browser/issues/52244
[Android] Hide Brave Rewards UI elements when disabled by policy
Implements policy-based visibility control for Brave Rewards across
Android UI. When BRAVE_REWARDS_DISABLED policy is active via Brave
Origin subscription settings, all rewards-related UI elements are
now properly hidden.
Resolves: https://github.com/brave/brave-browser/issues/51826
[Android] Adds kBraveFreshNtpAfterIdleExpirement experiment flag.
That commit adds the flag with parameters and a functionality
to use cached flags on Android.
Resolves: https://github.com/brave/brave-browser/issues/50567