diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni index d04fa1bf955..d6eca4d7725 100644 --- a/android/brave_java_sources.gni +++ b/android/brave_java_sources.gni @@ -298,6 +298,7 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/onboarding/v2/OnboardingV2Fragment.java", "../../brave/android/java/org/chromium/chrome/browser/onboarding/v2/OnboardingV2PagerAdapter.java", "../../brave/android/java/org/chromium/chrome/browser/partnercustomizations/CloseBraveManager.java", + "../../brave/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordEntryPreference.java", "../../brave/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java", "../../brave/android/java/org/chromium/chrome/browser/playlist/PlaylistHostActivity.java", "../../brave/android/java/org/chromium/chrome/browser/playlist/PlaylistServiceFactoryAndroid.java", diff --git a/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordEntryPreference.java b/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordEntryPreference.java new file mode 100644 index 00000000000..5d8dbf4ae9c --- /dev/null +++ b/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordEntryPreference.java @@ -0,0 +1,67 @@ +// Copyright (c) 2026 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package org.chromium.chrome.browser.password_manager.settings; + +import android.content.Context; +import android.graphics.drawable.BitmapDrawable; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.ui.favicon.FaviconHelper; +import org.chromium.url.GURL; + +/** + * A Preference that lazily loads a favicon when it becomes visible on screen. The favicon is + * fetched only once when the view is first bound, and cached via {@link Preference#setIcon} for + * subsequent rebinds. + */ +@NullMarked +public class PasswordEntryPreference extends Preference { + private final GURL mOriginUrl; + private final FaviconHelper mFaviconHelper; + private final FaviconHelper.DefaultFaviconHelper mDefaultFaviconHelper; + private final Profile mProfile; + + public PasswordEntryPreference( + Context context, + String originUrl, + FaviconHelper faviconHelper, + FaviconHelper.DefaultFaviconHelper defaultFaviconHelper, + Profile profile) { + super(context); + mOriginUrl = new GURL(originUrl); + mFaviconHelper = faviconHelper; + mDefaultFaviconHelper = defaultFaviconHelper; + mProfile = profile; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + if (getIcon() == null) { + int faviconSizePixels = + getContext() + .getResources() + .getDimensionPixelSize(R.dimen.omnibox_suggestion_favicon_size); + mFaviconHelper.getForeignFaviconImageForURL( + mProfile, + mOriginUrl, + faviconSizePixels, + (bitmap, iconUrl) -> { + if (bitmap == null) { + bitmap = + mDefaultFaviconHelper.getDefaultFaviconBitmap( + getContext(), iconUrl, true, false); + } + setIcon(new BitmapDrawable(getContext().getResources(), bitmap)); + }); + } + } +} diff --git a/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java b/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java index ff17a2c9020..5412446d40c 100644 --- a/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java +++ b/android/java/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java @@ -34,6 +34,7 @@ import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.supplier.MonotonicObservableSupplier; import org.chromium.base.supplier.ObservableSuppliers; import org.chromium.base.supplier.SettableMonotonicObservableSupplier; +import org.chromium.build.annotations.EnsuresNonNull; import org.chromium.build.annotations.Initializer; import org.chromium.build.annotations.MonotonicNonNull; import org.chromium.build.annotations.NullMarked; @@ -47,6 +48,7 @@ import org.chromium.chrome.browser.settings.ChromeBaseSettingsFragment; import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate; import org.chromium.chrome.browser.settings.MainSettings; import org.chromium.chrome.browser.settings.search.ChromeBaseSearchIndexProvider; +import org.chromium.chrome.browser.ui.favicon.FaviconHelper; import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; import org.chromium.components.browser_ui.settings.SearchUtils; import org.chromium.components.browser_ui.settings.SearchViewProvider; @@ -116,6 +118,8 @@ public class PasswordSettings extends ChromeBaseSettingsFragment private @Nullable Preference mLinkPref; private /*@Nullable*/ Menu mMenu; private @Nullable Preference mExportPasswordsPreference; + private @MonotonicNonNull FaviconHelper mFaviconHelper; + private @MonotonicNonNull FaviconHelper.DefaultFaviconHelper mDefaultFaviconHelper; private @ManagePasswordsReferrer int mManagePasswordsReferrer; private final SettableMonotonicObservableSupplier mPageTitle = @@ -134,6 +138,7 @@ public class PasswordSettings extends ChromeBaseSettingsFragment } @Override + @EnsuresNonNull({"mFaviconHelper", "mDefaultFaviconHelper"}) public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { mExportFlow.onCreate( savedInstanceState, @@ -190,6 +195,9 @@ public class PasswordSettings extends ChromeBaseSettingsFragment }); mPageTitle.set(getString(R.string.password_manager_settings_title)); + mFaviconHelper = new FaviconHelper(); + mDefaultFaviconHelper = new FaviconHelper.DefaultFaviconHelper(); + // Load preferences from XML instead of creating programmatically SettingsUtils.addPreferencesFromResource(this, R.xml.brave_password_settings_preferences); @@ -540,7 +548,13 @@ public class PasswordSettings extends ChromeBaseSettingsFragment if (shouldBeFiltered(url, name)) { continue; // The current password won't show with the active filter, try the next. } - Preference preference = new Preference(getStyledContext()); + PasswordEntryPreference preference = + new PasswordEntryPreference( + getStyledContext(), + saved.getOriginUrl(), + assumeNonNull(mFaviconHelper), + assumeNonNull(mDefaultFaviconHelper), + getProfile()); preference.setTitle(url); preference.setOnPreferenceClickListener(this); preference.setSummary(name); @@ -671,6 +685,7 @@ public class PasswordSettings extends ChromeBaseSettingsFragment // by the system. if (getActivity().isFinishing()) { PasswordManagerHandlerProvider.getForProfile(getProfile()).removeObserver(this); + assumeNonNull(mFaviconHelper).destroy(); } } diff --git a/android/javatests/org/chromium/chrome/browser/password_manager/settings/PasswordSettingsTestHelper.java b/android/javatests/org/chromium/chrome/browser/password_manager/settings/PasswordSettingsTestHelper.java index b0617462c91..e00a8d05e1c 100644 --- a/android/javatests/org/chromium/chrome/browser/password_manager/settings/PasswordSettingsTestHelper.java +++ b/android/javatests/org/chromium/chrome/browser/password_manager/settings/PasswordSettingsTestHelper.java @@ -27,15 +27,26 @@ import java.util.Arrays; /** Helper functions used in various password settings test suites. */ class PasswordSettingsTestHelper { static final SavedPasswordEntry ZEUS_ON_EARTH = - new SavedPasswordEntry("http://www.phoenicia.gr", "Zeus", "Europa"); + new SavedPasswordEntry( + "http://www.phoenicia.gr", "http://www.phoenicia.gr", "Zeus", "Europa"); static final SavedPasswordEntry ARES_AT_OLYMP = - new SavedPasswordEntry("https://1-of-12.olymp.gr", "Ares", "God-o'w@r"); + new SavedPasswordEntry( + "https://1-of-12.olymp.gr", "https://1-of-12.olymp.gr", "Ares", "God-o'w@r"); static final SavedPasswordEntry PHOBOS_AT_OLYMP = - new SavedPasswordEntry("https://visitor.olymp.gr", "Phobos-son-of-ares", "G0d0fF34r"); + new SavedPasswordEntry( + "https://visitor.olymp.gr", + "https://visitor.olymp.gr", + "Phobos-son-of-ares", + "G0d0fF34r"); static final SavedPasswordEntry DEIMOS_AT_OLYMP = - new SavedPasswordEntry("https://visitor.olymp.gr", "Deimops-Ares-son", "G0d0fT3rr0r"); + new SavedPasswordEntry( + "https://visitor.olymp.gr", + "https://visitor.olymp.gr", + "Deimops-Ares-son", + "G0d0fT3rr0r"); static final SavedPasswordEntry HADES_AT_UNDERWORLD = - new SavedPasswordEntry("https://underworld.gr", "", "C3rb3rus"); + new SavedPasswordEntry( + "https://underworld.gr", "https://underworld.gr", "", "C3rb3rus"); static final SavedPasswordEntry[] GREEK_GODS = { ZEUS_ON_EARTH, ARES_AT_OLYMP, PHOBOS_AT_OLYMP, DEIMOS_AT_OLYMP, HADES_AT_UNDERWORLD, }; diff --git a/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUiView.java b/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUiView.java index 2ab22d371f1..0b5694a4a3d 100644 --- a/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUiView.java +++ b/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUiView.java @@ -29,9 +29,10 @@ public final class PasswordUiView implements PasswordManagerHandler { @CalledByNative private static SavedPasswordEntry createSavedPasswordEntry( @JniType("std::string") String url, + @JniType("std::string") String originUrl, @JniType("std::u16string") String name, @JniType("std::u16string") String password) { - return new SavedPasswordEntry(url, name, password); + return new SavedPasswordEntry(url, originUrl, name, password); } // Pointer to native implementation, set to 0 in destroy(). diff --git a/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/SavedPasswordEntry.java b/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/SavedPasswordEntry.java index 28fced005a4..9c4c12bb37e 100644 --- a/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/SavedPasswordEntry.java +++ b/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/SavedPasswordEntry.java @@ -23,11 +23,13 @@ import org.chromium.build.annotations.NullMarked; @NullMarked public final class SavedPasswordEntry { private final String mUrl; + private final String mOriginUrl; private final String mName; private final String mPassword; - public SavedPasswordEntry(String url, String name, String password) { + public SavedPasswordEntry(String url, String originUrl, String name, String password) { mUrl = url; + mOriginUrl = originUrl; mName = name; mPassword = password; } @@ -36,6 +38,10 @@ public final class SavedPasswordEntry { return mUrl; } + public String getOriginUrl() { + return mOriginUrl; + } + public String getUserName() { return mName; } diff --git a/browser/password_manager/android/password_ui_view_android.cc b/browser/password_manager/android/password_ui_view_android.cc index e2af8902b02..818e2e1c9be 100644 --- a/browser/password_manager/android/password_ui_view_android.cc +++ b/browser/password_manager/android/password_ui_view_android.cc @@ -172,10 +172,11 @@ ScopedJavaLocalRef PasswordUiViewAndroid::GetSavedPasswordEntry( DCHECK_EQ(State::ALIVE, state_); if (static_cast(index) >= passwords_.size()) { return Java_PasswordUiView_createSavedPasswordEntry( - env, std::string(), std::u16string(), std::u16string()); + env, std::string(), std::string(), std::u16string(), std::u16string()); } return Java_PasswordUiView_createSavedPasswordEntry( env, password_manager::GetShownOrigin(passwords_[index]), + password_manager::GetShownUrl(passwords_[index]).spec(), passwords_[index].username, passwords_[index].password); } diff --git a/browser/password_manager/android/test_support/java/src/org/chromium/chrome/browser/password_manager/FakePasswordManagerHandler.java b/browser/password_manager/android/test_support/java/src/org/chromium/chrome/browser/password_manager/FakePasswordManagerHandler.java index 0e2caa0d06f..01a43e1ae52 100644 --- a/browser/password_manager/android/test_support/java/src/org/chromium/chrome/browser/password_manager/FakePasswordManagerHandler.java +++ b/browser/password_manager/android/test_support/java/src/org/chromium/chrome/browser/password_manager/FakePasswordManagerHandler.java @@ -88,7 +88,7 @@ public final class FakePasswordManagerHandler implements PasswordManagerHandler @Override public void insertPasswordEntryForTesting(String origin, String username, String password) { - mSavedPasswords.add(new SavedPasswordEntry(origin, username, password)); + mSavedPasswords.add(new SavedPasswordEntry(origin, origin, username, password)); } // Pretends that the updated lists are |mSavedPasswords| for the saved passwords and an