Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 39aa73de authored by Carmelo Messina's avatar Carmelo Messina
Browse files

Android-Import-Passwords: merged with Restore-chrome-password-store

parent 454dfbf1
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -293,7 +293,6 @@ Enable-component-updater.patch
Android-fonts-fingerprinting-mitigation.patch
Disable-Android-Tab-Declutter.patch
Disable-CSSDynamicRangeLimit.patch
Android-Import-Passwords.patch
Disable-prefers-reduced-motion.patch
Disable-css-preferred-text-scale.patch
Disable-Viewport-Segments.patch
+0 −353
Original line number Diff line number Diff line
From: uazo <uazo@users.noreply.github.com>
Date: Mon, 26 May 2025 12:17:48 +0000
Subject: Android Import Passwords

Adds the possibility of importing a csv file containing the list of passwords,
with the same functionality available on desktops.

License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html
---
 ...e_password_preferences_action_bar_menu.xml |  6 ++
 .../settings/PasswordSettings.java            | 34 +++++++++
 .../settings/PasswordManagerHandler.java      |  3 +
 .../settings/PasswordUiView.java              |  8 +++
 .../android/password_ui_view_android.cc       | 71 +++++++++++++++++++
 .../android/password_ui_view_android.h        | 20 +++++-
 .../Import-Password-Android.grdp              |  9 +++
 .../core/browser/import/password_importer.cc  | 11 +++
 8 files changed, 161 insertions(+), 1 deletion(-)
 create mode 100644 chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Import-Password-Android.grdp

diff --git a/chrome/android/java/res/menu/save_password_preferences_action_bar_menu.xml b/chrome/android/java/res/menu/save_password_preferences_action_bar_menu.xml
--- a/chrome/android/java/res/menu/save_password_preferences_action_bar_menu.xml
+++ b/chrome/android/java/res/menu/save_password_preferences_action_bar_menu.xml
@@ -22,6 +22,12 @@ found in the LICENSE file.
         android:visibility="gone"
         app:showAsAction="ifRoom"/>
 
+    <item
+        android:id="@+id/import_passwords"
+        android:title="@string/password_settings_import_action_title"
+        android:contentDescription="@string/password_settings_import_action_description"
+        app:showAsAction="never"/>
+
     <item
         android:id="@+id/export_passwords"
         android:title="@string/password_settings_export_action_title"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java
@@ -57,6 +57,9 @@ import org.chromium.components.sync.PassphraseType;
 import org.chromium.components.sync.SyncService;
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.ui.text.SpanApplier;
+import org.chromium.ui.base.ActivityWindowAndroid;
+import org.chromium.ui.base.ActivityIntentRequestTrackerDelegate;
+import org.chromium.ui.base.IntentRequestTracker;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -151,6 +154,8 @@ public class PasswordSettings extends ChromeBaseSettingsFragment
 
     /** For controlling the UX flow of exporting passwords. */
     private final ExportFlow mExportFlow = new ExportFlow(PasswordAccessLossWarningType.NONE);
+    private ActivityWindowAndroid mWindowAndroid;
+    private IntentRequestTracker mIntentRequestTracker;
 
     public ExportFlow getExportFlowForTesting() {
         return mExportFlow;
@@ -199,6 +204,19 @@ public class PasswordSettings extends ChromeBaseSettingsFragment
 
         mManagePasswordsReferrer = getReferrerFromInstanceStateOrLaunchBundle(savedInstanceState);
 
+        mIntentRequestTracker = IntentRequestTracker.createFromDelegate(
+                                new ActivityIntentRequestTrackerDelegate(getActivity()) {
+                                    @Override
+                                    public boolean startActivityForResult(@Nullable Intent intent, int requestCode) {
+                                        PasswordSettings.this.startActivityForResult(intent, requestCode);
+                                        return true;
+                                    }
+                                });
+        mWindowAndroid = new ActivityWindowAndroid(
+            getActivity(), /* listenToActivityState */ true,
+            mIntentRequestTracker, /*InsetObserver*/ null,
+            /* trackOcclusion= */ true);
+
         if (savedInstanceState == null) return;
 
         if (savedInstanceState.containsKey(SAVED_STATE_SEARCH_QUERY)) {
@@ -263,6 +281,12 @@ public class PasswordSettings extends ChromeBaseSettingsFragment
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         int id = item.getItemId();
+        if (id == R.id.import_passwords) {
+            PasswordManagerHandlerProvider.getForProfile(getProfile())
+                    .getPasswordManagerHandler()
+                    .startImporting(mWindowAndroid);
+            return true;
+        }
         if (id == R.id.export_passwords) {
             RecordHistogram.recordEnumeratedHistogram(
                     mExportFlow.getExportEventHistogramName(),
@@ -506,6 +530,7 @@ public class PasswordSettings extends ChromeBaseSettingsFragment
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent intent) {
         super.onActivityResult(requestCode, resultCode, intent);
+        mWindowAndroid.getIntentRequestTracker().onActivityResult(requestCode, resultCode, intent);
         if (requestCode != PASSWORD_EXPORT_INTENT_REQUEST_CODE) return;
         if (resultCode != Activity.RESULT_OK) return;
         if (intent == null || intent.getData() == null) return;
@@ -517,12 +542,21 @@ public class PasswordSettings extends ChromeBaseSettingsFragment
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         mExportFlow.onSaveInstanceState(outState);
+        mWindowAndroid.getIntentRequestTracker().saveInstanceState(outState);
         if (mSearchQuery != null) {
             outState.putString(SAVED_STATE_SEARCH_QUERY, mSearchQuery);
         }
         outState.putInt(PasswordManagerHelper.MANAGE_PASSWORDS_REFERRER, mManagePasswordsReferrer);
     }
 
+    @Override
+    public void onRequestPermissionsResult(
+            int requestCode, String[] permissions, int[] grantResults) {
+        if (mWindowAndroid.handlePermissionResult(requestCode, permissions, grantResults))
+            return;
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+    }
+
     @Override
     public void onDestroy() {
         super.onDestroy();
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordManagerHandler.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordManagerHandler.java
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordManagerHandler.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordManagerHandler.java
@@ -9,6 +9,7 @@ import android.content.Context;
 import org.chromium.base.Callback;
 import org.chromium.base.IntStringCallback;
 import org.chromium.build.annotations.NullMarked;
+import org.chromium.ui.base.WindowAndroid;
 
 /**
  * Interface for retrieving passwords and password exceptions (websites for which Chrome should not
@@ -80,4 +81,6 @@ public interface PasswordManagerHandler {
      * @return Returns true if the request to fetch the passwords is still pending.
      */
     boolean isWaitingForPasswordStore();
+
+    boolean startImporting(WindowAndroid window);
 }
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUiView.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUiView.java
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUiView.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUiView.java
@@ -12,6 +12,7 @@ import org.jni_zero.NativeMethods;
 
 import org.chromium.base.Callback;
 import org.chromium.base.IntStringCallback;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.chrome.browser.profiles.Profile;
 
@@ -158,6 +159,12 @@ public final class PasswordUiView implements PasswordManagerHandler {
         }
     }
 
+    @Override
+    public boolean startImporting(WindowAndroid window) {
+        return PasswordUiViewJni.get()
+                .startImporting(mNativePasswordUiViewAndroid, PasswordUiView.this, window);
+    }
+
     @NativeMethods
     interface Natives {
         long init(PasswordUiView caller, @JniType("Profile*") Profile profile);
@@ -190,6 +197,7 @@ public final class PasswordUiView implements PasswordManagerHandler {
         String getTrustedVaultLearnMoreURL();
 
         boolean isWaitingForPasswordStore(long nativePasswordUiViewAndroid, PasswordUiView caller);
+        boolean startImporting(long nativePasswordUiViewAndroid, PasswordUiView caller, WindowAndroid window);
 
         void destroy(long nativePasswordUiViewAndroid, PasswordUiView caller);
 
diff --git a/chrome/browser/password_manager/android/password_ui_view_android.cc b/chrome/browser/password_manager/android/password_ui_view_android.cc
--- a/chrome/browser/password_manager/android/password_ui_view_android.cc
+++ b/chrome/browser/password_manager/android/password_ui_view_android.cc
@@ -41,6 +41,11 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
+#include "chrome/browser/ui/chrome_select_file_policy.h"
+#include "ui/shell_dialogs/selected_file_info.h"
+#include "ui/shell_dialogs/select_file_dialog.h"
+#include "ui/android/window_android.h"
+
 // Must come after other includes, because FromJniType() uses Profile.
 #include "chrome/browser/password_manager/android/jni_headers/PasswordUiView_jni.h"
 
@@ -123,6 +128,8 @@ PasswordUiViewAndroid::PasswordUiViewAndroid(
 
 PasswordUiViewAndroid::~PasswordUiViewAndroid() {
   saved_passwords_presenter_.RemoveObserver(this);
+  if (select_file_dialog_)
+    select_file_dialog_->ListenerDestroyed();
 }
 
 void PasswordUiViewAndroid::Destroy(JNIEnv*, const JavaRef<jobject>&) {
@@ -314,6 +321,70 @@ jboolean PasswordUiViewAndroid::IsWaitingForPasswordStore(
   return saved_passwords_presenter_.IsWaitingForPasswordStore();
 }
 
+void PasswordUiViewAndroid::ImportFinished(
+    const password_manager::ImportResults& results) {
+  std::string error;
+  if (results.status == password_manager::ImportResults::Status::SUCCESS)
+    error = "Success.";
+  else if (results.status == password_manager::ImportResults::Status::IO_ERROR)
+    error = "Failed to read provided file.";
+  else if (results.status == password_manager::ImportResults::Status::BAD_FORMAT)
+    error = "Header is missing, invalid or could not be read.";
+  else if (results.status == password_manager::ImportResults::Status::NUM_PASSWORDS_EXCEEDED)
+    error = "Too many passwords.";
+  else
+    error = "UNKNOWN ERROR.";
+
+  std::stringstream message;
+  message << error << " Imported " << results.number_imported << " passwords.";
+  auto result = message.str();
+
+  select_file_dialog_->ShowToast(result);
+
+  importer_.reset();
+}
+
+jboolean PasswordUiViewAndroid::StartImporting(
+    JNIEnv* env,
+    const base::android::JavaRef<jobject>& obj,
+    const JavaParamRef<jobject>& java_window) {
+  ui::WindowAndroid* window =
+      ui::WindowAndroid::FromJavaWindowAndroid(java_window);
+  CHECK(window);
+
+  select_file_dialog_ = ui::SelectFileDialog::Create(
+    this, std::make_unique<ChromeSelectFilePolicy>(nullptr));
+
+  ui::SelectFileDialog::FileTypeInfo file_type_info;
+
+  const std::vector<std::u16string> v_accept_types = { u"application/octet-stream" };
+  select_file_dialog_->SetAcceptTypes(v_accept_types);
+
+  select_file_dialog_->SelectFile(
+        ui::SelectFileDialog::SELECT_OPEN_FILE,
+        std::u16string(),
+        base::FilePath(),
+        &file_type_info,
+        0,
+        base::FilePath::StringType(),
+        window);
+
+  return true;
+}
+
+void PasswordUiViewAndroid::FileSelectionCanceled() {}
+
+void PasswordUiViewAndroid::FileSelected(const ui::SelectedFileInfo& file, int index) {
+  base::FilePath path = file.path();
+
+  importer_ =
+    std::make_unique<password_manager::PasswordImporter>(&saved_passwords_presenter_);
+  importer_->Import(path,
+    password_manager::PasswordForm::Store::kProfileStore,
+    base::BindOnce(&PasswordUiViewAndroid::ImportFinished,
+                     base::Unretained(this)));
+}
+
 // static
 static jlong JNI_PasswordUiView_Init(JNIEnv* env,
                                      const JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/password_manager/android/password_ui_view_android.h b/chrome/browser/password_manager/android/password_ui_view_android.h
--- a/chrome/browser/password_manager/android/password_ui_view_android.h
+++ b/chrome/browser/password_manager/android/password_ui_view_android.h
@@ -21,6 +21,8 @@
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/password_manager/core/browser/password_store/password_store_consumer.h"
 #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
+#include "components/password_manager/core/browser/import/password_importer.h"
+#include "ui/shell_dialogs/select_file_dialog.h"
 
 class Profile;
 
@@ -31,7 +33,8 @@ class CredentialProviderInterface;
 // PasswordUIView for Android, contains jni hooks that allows Android UI to
 // display passwords and route UI commands back to SavedPasswordsPresenter.
 class PasswordUiViewAndroid
-    : public password_manager::SavedPasswordsPresenter::Observer {
+    : public password_manager::SavedPasswordsPresenter::Observer,
+      public ui::SelectFileDialog::Listener {
  public:
   // Result of transforming a vector of PasswordForms into their CSV
   // description and writing that to disk.
@@ -95,6 +98,18 @@ class PasswordUiViewAndroid
       const base::android::JavaParamRef<jobject>& obj);
   jboolean IsWaitingForPasswordStore(JNIEnv* env,
                                      const base::android::JavaRef<jobject>&);
+
+  void FileSelected(const ui::SelectedFileInfo& file,
+                    int index) override;
+  void FileSelectionCanceled() override;
+
+  void ImportFinished(
+       const password_manager::ImportResults& results);
+
+  jboolean StartImporting(JNIEnv* env,
+                          const base::android::JavaRef<jobject>& obj,
+                          const base::android::JavaParamRef<jobject>& java_window);
+
   // Destroy the native implementation.
   void Destroy(JNIEnv*, const base::android::JavaRef<jobject>&);
 
@@ -162,6 +177,9 @@ class PasswordUiViewAndroid
   std::vector<password_manager::CredentialUIEntry> passwords_;
   std::vector<password_manager::CredentialUIEntry> blocked_sites_;
 
+  scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
+  std::unique_ptr<password_manager::PasswordImporter> importer_;
+
   // If not null, passwords for exporting will be obtained from
   // |*credential_provider_for_testing_|, otherwise from
   // |saved_passwords_presenter_|. This must remain null in production code.
diff --git a/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Import-Password-Android.grdp b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Import-Password-Android.grdp
new file mode 100644
--- /dev/null
+++ b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Import-Password-Android.grdp
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+    <message name="IDS_PASSWORD_SETTINGS_IMPORT_ACTION_TITLE" desc="The title of a menu item to trigger importing passwords from the password settings.">
+        Import passwords…
+    </message>
+    <message name="IDS_PASSWORD_SETTINGS_IMPORT_ACTION_DESCRIPTION" desc="The description of a menu item to trigger importing passwords from the password settings.">
+        Import passwords stored with Chrome
+    </message>
+</grit-part>
diff --git a/components/password_manager/core/browser/import/password_importer.cc b/components/password_manager/core/browser/import/password_importer.cc
--- a/components/password_manager/core/browser/import/password_importer.cc
+++ b/components/password_manager/core/browser/import/password_importer.cc
@@ -76,6 +76,17 @@ const int32_t kMaxFileSizeBytes = 1000 * 1024;
 // optional string. The string will be present if the status is SUCCESS.
 base::expected<std::string, ImportResults::Status> ReadFileToString(
     const base::FilePath& path) {
+#if BUILDFLAG(IS_ANDROID)
+  if (path.IsContentUri()) {
+    base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+    auto fileLength = file.GetLength();
+    std::vector<char> buffer(fileLength);
+    file.ReadAtCurrentPos(base::as_writable_byte_span(buffer));
+
+    std::string file_contents(buffer.begin(), buffer.end());
+    return std::move(file_contents);
+  }
+#endif
   std::optional<int64_t> file_size = base::GetFileSize(path);
 
   if (file_size.has_value()) {
--