354 lines
17 KiB
Diff
354 lines
17 KiB
Diff
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
|
|
@@ -75,6 +75,17 @@ const int32_t kMaxFileSizeBytes = 150 * 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()) {
|
|
--
|