From: uazo 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"/> + + ListenerDestroyed(); } void PasswordUiViewAndroid::Destroy(JNIEnv*, const JavaRef&) { @@ -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& obj, + const JavaParamRef& java_window) { + ui::WindowAndroid* window = + ui::WindowAndroid::FromJavaWindowAndroid(java_window); + CHECK(window); + + select_file_dialog_ = ui::SelectFileDialog::Create( + this, std::make_unique(nullptr)); + + ui::SelectFileDialog::FileTypeInfo file_type_info; + + const std::vector 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(&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& 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& obj); jboolean IsWaitingForPasswordStore(JNIEnv* env, const base::android::JavaRef&); + + 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& obj, + const base::android::JavaParamRef& java_window); + // Destroy the native implementation. void Destroy(JNIEnv*, const base::android::JavaRef&); @@ -162,6 +177,9 @@ class PasswordUiViewAndroid std::vector passwords_; std::vector blocked_sites_; + scoped_refptr select_file_dialog_; + std::unique_ptr 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 @@ + + + + Import passwords… + + + Import passwords stored with Chrome + + 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 @@ -75,6 +75,17 @@ const int32_t kMaxFileSizeBytes = 150 * 1024; // optional string. The string will be present if the status is SUCCESS. base::expected 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 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 file_size = base::GetFileSize(path); if (file_size.has_value()) { --