1586 lines
77 KiB
Diff
1586 lines
77 KiB
Diff
From: csagan5 <32685696+csagan5@users.noreply.github.com>
|
|
Date: Wed, 1 Aug 2018 09:19:40 +0200
|
|
Subject: Add bookmark import/export actions
|
|
|
|
Add bookmark import/export actions in bookmarks activity and page
|
|
Reduce permissions needed for bookmarks import/export
|
|
Completely remove contacts picker permission from the file dialog
|
|
|
|
Requires: Adds-support-for-writing-URIs.patch
|
|
Requires: Restore-BookmarkToolbar-setCurrentFolder.patch
|
|
|
|
License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
|
|
---
|
|
chrome/android/java/AndroidManifest.xml | 1 -
|
|
.../menu/bookmark_toolbar_menu_improved.xml | 14 +
|
|
.../browser/TabbedModeTabDelegateFactory.java | 5 +-
|
|
.../app/bookmarks/BookmarkActivity.java | 20 ++
|
|
.../native_page/NativePageFactory.java | 7 +-
|
|
chrome/browser/BUILD.gn | 7 +
|
|
.../bookmarks/android/bookmark_bridge.cc | 193 ++++++++++++
|
|
.../bookmarks/android/bookmark_bridge.h | 30 +-
|
|
.../browser/bookmarks/BookmarkBridge.java | 39 +++
|
|
.../browser/bookmarks/BookmarkDelegate.java | 10 +
|
|
.../bookmarks/BookmarkManagerCoordinator.java | 9 +
|
|
.../bookmarks/BookmarkManagerMediator.java | 283 ++++++++++++++++++
|
|
.../browser/bookmarks/BookmarkPage.java | 8 +-
|
|
.../browser/bookmarks/BookmarkToolbar.java | 27 ++
|
|
.../bookmarks/BookmarkToolbarMediator.java | 4 +
|
|
.../bookmarks/BookmarkToolbarProperties.java | 8 +-
|
|
.../bookmarks/BookmarkToolbarViewBinder.java | 6 +
|
|
.../dialogs/DownloadLocationCustomView.java | 8 +-
|
|
.../DownloadLocationDialogCoordinator.java | 10 +-
|
|
.../flags/android/chrome_feature_list.cc | 1 +
|
|
.../browser/flags/ChromeFeatureList.java | 1 +
|
|
chrome/browser/importer/profile_writer.cc | 10 +
|
|
chrome/browser/importer/profile_writer.h | 6 +
|
|
.../preferences/ChromePreferenceKeys.java | 3 +
|
|
.../strings/android_chrome_strings.grd | 18 ++
|
|
chrome/utility/BUILD.gn | 7 +
|
|
.../importer/bookmarks_file_importer.cc | 4 +
|
|
.../headless_select_file_dialog.cc | 4 +
|
|
.../content/content_bookmark_parser_utils.cc | 3 +
|
|
.../add-bookmark-import-export-actions.inc | 12 +
|
|
.../add-bookmark-import-export-actions.inc | 4 +
|
|
.../add-bookmark-import-export-actions.inc | 1 +
|
|
.../chromium/ui/base/SelectFileDialog.java | 24 +-
|
|
.../java/strings/android_ui_strings.grd | 3 +
|
|
ui/shell_dialogs/select_file_dialog.h | 2 +
|
|
.../select_file_dialog_android.cc | 6 +
|
|
ui/shell_dialogs/select_file_dialog_android.h | 2 +
|
|
ui/shell_dialogs/select_file_dialog_linux.cc | 4 +
|
|
ui/shell_dialogs/select_file_dialog_linux.h | 2 +
|
|
ui/shell_dialogs/select_file_dialog_win.cc | 5 +
|
|
40 files changed, 790 insertions(+), 21 deletions(-)
|
|
create mode 100644 cromite_flags/chrome/browser/about_flags_cc/add-bookmark-import-export-actions.inc
|
|
create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/add-bookmark-import-export-actions.inc
|
|
create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/add-bookmark-import-export-actions.inc
|
|
|
|
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
|
|
--- a/chrome/android/java/AndroidManifest.xml
|
|
+++ b/chrome/android/java/AndroidManifest.xml
|
|
@@ -62,7 +62,6 @@ by a child template that "extends" this file.
|
|
<uses-permission-sdk-23 android:name="android.permission.BLUETOOTH_SCAN"
|
|
android:usesPermissionFlags="neverForLocation"/>
|
|
|
|
- <uses-permission-sdk-23 android:name="android.permission.READ_CONTACTS"/>
|
|
<uses-permission-sdk-23 android:name="android.permission.REORDER_TASKS"/>
|
|
|
|
<!-- Needed for allowing downloaded APKs to be installed. -->
|
|
diff --git a/chrome/android/java/res/menu/bookmark_toolbar_menu_improved.xml b/chrome/android/java/res/menu/bookmark_toolbar_menu_improved.xml
|
|
--- a/chrome/android/java/res/menu/bookmark_toolbar_menu_improved.xml
|
|
+++ b/chrome/android/java/res/menu/bookmark_toolbar_menu_improved.xml
|
|
@@ -51,6 +51,20 @@ found in the LICENSE file.
|
|
android:title="@string/create_new_folder"
|
|
app:showAsAction="ifRoom"
|
|
app:iconTint="@color/default_icon_color_secondary_tint_list" />
|
|
+ <item
|
|
+ android:id="@+id/import_menu_id"
|
|
+ android:icon="@drawable/ic_folder_blue_24dp"
|
|
+ android:title="@string/import_bookmarks"
|
|
+ android:visible="true"
|
|
+ app:showAsAction="ifRoom"
|
|
+ app:iconTint="@color/default_icon_color_tint_list" />
|
|
+ <item
|
|
+ android:id="@+id/export_menu_id"
|
|
+ android:icon="@drawable/ic_file_download_white_24dp"
|
|
+ android:title="@string/export_bookmarks"
|
|
+ android:visible="true"
|
|
+ app:showAsAction="ifRoom"
|
|
+ app:iconTint="@color/default_icon_color_tint_list" />
|
|
<item
|
|
android:id="@+id/close_menu_id"
|
|
android:icon="@drawable/material_ic_close_24dp"
|
|
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
|
|
@@ -11,6 +11,7 @@ import org.chromium.base.supplier.NonNullObservableSupplier;
|
|
import org.chromium.base.supplier.OneshotSupplier;
|
|
import org.chromium.build.annotations.NullMarked;
|
|
import org.chromium.build.annotations.Nullable;
|
|
+import org.chromium.chrome.browser.app.ChromeActivity;
|
|
import org.chromium.chrome.browser.app.tab_activity_glue.ActivityTabWebContentsDelegateAndroid;
|
|
import org.chromium.chrome.browser.back_press.BackPressManager;
|
|
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
|
|
@@ -61,7 +62,7 @@ import java.util.function.Supplier;
|
|
*/
|
|
@NullMarked
|
|
public class TabbedModeTabDelegateFactory implements TabDelegateFactory {
|
|
- private final Activity mActivity;
|
|
+ private final ChromeActivity mActivity;
|
|
private final BrowserControlsVisibilityDelegate mAppBrowserControlsVisibilityDelegate;
|
|
private final Supplier<@Nullable ShareDelegate> mShareDelegateSupplier;
|
|
private final Supplier<@Nullable EphemeralTabCoordinator> mEphemeralTabCoordinatorSupplier;
|
|
@@ -93,7 +94,7 @@ public class TabbedModeTabDelegateFactory implements TabDelegateFactory {
|
|
private final RecentlyClosedEntriesManager mRecentlyClosedEntriesManager;
|
|
|
|
public TabbedModeTabDelegateFactory(
|
|
- Activity activity,
|
|
+ ChromeActivity activity,
|
|
BrowserControlsVisibilityDelegate appBrowserControlsVisibilityDelegate,
|
|
Supplier<@Nullable ShareDelegate> shareDelegateSupplier,
|
|
Supplier<@Nullable EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
|
|
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java
|
|
@@ -6,6 +6,8 @@ package org.chromium.chrome.browser.app.bookmarks;
|
|
|
|
import static org.chromium.build.NullUtil.assumeNonNull;
|
|
|
|
+import android.os.Bundle;
|
|
+
|
|
import android.content.ComponentName;
|
|
import android.content.Intent;
|
|
import android.text.TextUtils;
|
|
@@ -125,6 +127,9 @@ public class BookmarkActivity extends SnackbarActivity {
|
|
mOnKeyDownHandler =
|
|
BackPressHelper.create(
|
|
this, getOnBackPressedDispatcher(), mBookmarkManagerCoordinator);
|
|
+
|
|
+ mBookmarkManagerCoordinator.setWindow(getWindowAndroid(),
|
|
+ getModalDialogManagerSupplier().get());
|
|
}
|
|
|
|
@Override
|
|
@@ -167,6 +172,21 @@ public class BookmarkActivity extends SnackbarActivity {
|
|
}
|
|
}
|
|
|
|
+ @Override
|
|
+ protected void onSaveInstanceState(Bundle outState) {
|
|
+ super.onSaveInstanceState(outState);
|
|
+
|
|
+ getWindowAndroid().getIntentRequestTracker().saveInstanceState(outState);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onRequestPermissionsResult(
|
|
+ int requestCode, String[] permissions, int[] grantResults) {
|
|
+ if (getWindowAndroid().handlePermissionResult(requestCode, permissions, grantResults))
|
|
+ return;
|
|
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
+ }
|
|
+
|
|
@Override
|
|
protected ModalDialogManager createModalDialogManager() {
|
|
return new ModalDialogManager(new AppModalPresenter(this), ModalDialogType.APP);
|
|
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
|
|
@@ -28,6 +28,7 @@ import org.chromium.build.annotations.NullMarked;
|
|
import org.chromium.build.annotations.Nullable;
|
|
import org.chromium.chrome.R;
|
|
import org.chromium.chrome.browser.RecentlyClosedEntriesManager;
|
|
+import org.chromium.chrome.browser.app.ChromeActivity;
|
|
import org.chromium.chrome.browser.app.download.home.DownloadPage;
|
|
import org.chromium.chrome.browser.back_press.BackPressManager;
|
|
import org.chromium.chrome.browser.bookmarks.BookmarkPage;
|
|
@@ -82,7 +83,7 @@ import java.util.function.Supplier;
|
|
*/
|
|
@NullMarked
|
|
public class NativePageFactory {
|
|
- private final Activity mActivity;
|
|
+ private final ChromeActivity mActivity;
|
|
private final BottomSheetController mBottomSheetController;
|
|
private final BrowserControlsManager mBrowserControlsManager;
|
|
private final Supplier<@Nullable Tab> mCurrentTabSupplier;
|
|
@@ -108,7 +109,7 @@ public class NativePageFactory {
|
|
private final RecentlyClosedEntriesManager mRecentlyClosedEntriesManager;
|
|
|
|
public NativePageFactory(
|
|
- Activity activity,
|
|
+ ChromeActivity activity,
|
|
BottomSheetController sheetController,
|
|
BrowserControlsManager browserControlsManager,
|
|
Supplier<@Nullable Tab> currentTabSupplier,
|
|
@@ -189,7 +190,7 @@ public class NativePageFactory {
|
|
|
|
@VisibleForTesting
|
|
static class NativePageBuilder {
|
|
- private final Activity mActivity;
|
|
+ private final ChromeActivity mActivity;
|
|
private final BottomSheetController mBottomSheetController;
|
|
private final Supplier<NewTabPageCreationTracker> mNewTabPageCreationTracker;
|
|
private final BrowserControlsManager mBrowserControlsManager;
|
|
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
|
|
--- a/chrome/browser/BUILD.gn
|
|
+++ b/chrome/browser/BUILD.gn
|
|
@@ -1060,6 +1060,13 @@ static_library("browser") {
|
|
"webauthn/webauthn_switches.cc",
|
|
]
|
|
|
|
+ if (is_android) {
|
|
+ sources += [
|
|
+ "importer/profile_writer.cc",
|
|
+ "importer/profile_writer.h",
|
|
+ ]
|
|
+ }
|
|
+
|
|
configs += [
|
|
"//build/config/compiler:wexit_time_destructors",
|
|
"//build/config:precompiled_headers",
|
|
diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.cc b/chrome/browser/bookmarks/android/bookmark_bridge.cc
|
|
--- a/chrome/browser/bookmarks/android/bookmark_bridge.cc
|
|
+++ b/chrome/browser/bookmarks/android/bookmark_bridge.cc
|
|
@@ -64,6 +64,28 @@
|
|
#include "content/public/browser/web_contents.h"
|
|
#include "url/gurl.h"
|
|
|
|
+#include "base/android/content_uri_utils.h"
|
|
+#include "base/android/path_utils.h"
|
|
+#include "base/strings/utf_string_conversions.h"
|
|
+#include "chrome/browser/bookmarks/bookmark_html_writer.h"
|
|
+#include "chrome/browser/importer/profile_writer.h"
|
|
+#include "chrome/browser/platform_util.h"
|
|
+#include "chrome/browser/ui/select_file_policy/chrome_select_file_policy.h"
|
|
+#include "components/user_data_importer/common/imported_bookmark_entry.h"
|
|
+#include "components/user_data_importer/content/content_bookmark_parser.h"
|
|
+#include "components/user_data_importer/content/content_bookmark_parser_utils.h"
|
|
+#include "chrome/common/url_constants.h"
|
|
+#include "components/favicon_base/favicon_usage_data.h"
|
|
+#include "components/search_engines/template_url.h"
|
|
+#include "components/url_formatter/url_fixer.h"
|
|
+#include "ui/android/window_android.h"
|
|
+#include "base/task/task_traits.h"
|
|
+#include "base/task/thread_pool.h"
|
|
+#include "content/public/browser/browser_task_traits.h"
|
|
+#include "base/files/file_path.h"
|
|
+#include "ui/shell_dialogs/selected_file_info.h"
|
|
+#include "ui/shell_dialogs/select_file_dialog.h"
|
|
+
|
|
// Must come after all headers that specialize FromJniType() / ToJniType().
|
|
#include "chrome/browser/bookmarks/android/jni_headers/BookmarkBridge_jni.h"
|
|
|
|
@@ -293,6 +315,10 @@ BookmarkBridge::~BookmarkBridge() {
|
|
partner_bookmarks_shim_observation_.Reset();
|
|
bookmark_model_observation_.Reset();
|
|
profile_observation_.Reset();
|
|
+ // There may be pending file dialogs, we need to tell them that we've gone
|
|
+ // away so they don't try and call back to us.
|
|
+ if (select_file_dialog_)
|
|
+ select_file_dialog_->ListenerDestroyed();
|
|
}
|
|
|
|
void BookmarkBridge::Destroy(JNIEnv* env) {
|
|
@@ -841,6 +867,173 @@ int32_t BookmarkBridge::GetTotalBookmarkCount(JNIEnv* env,
|
|
return count;
|
|
}
|
|
|
|
+void BookmarkBridge::ImportBookmarks(JNIEnv* env,
|
|
+ const JavaRef<jobject>& obj,
|
|
+ const JavaRef<jobject>& java_window) {
|
|
+ DCHECK(IsLoaded());
|
|
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
|
+
|
|
+ ui::WindowAndroid* window =
|
|
+ ui::WindowAndroid::FromJavaWindowAndroid(java_window);
|
|
+ CHECK(window);
|
|
+
|
|
+ select_file_dialog_ = ui::SelectFileDialog::Create(
|
|
+ this, std::make_unique<ChromeSelectFilePolicy>(nullptr));
|
|
+
|
|
+ //NOTE: extension and description are not used on Android, thus not set
|
|
+ ui::SelectFileDialog::FileTypeInfo file_type_info;
|
|
+
|
|
+ const std::vector<std::u16string> v_accept_types = { u"text/html" };
|
|
+ select_file_dialog_->SetAcceptTypes(v_accept_types);
|
|
+
|
|
+ select_file_dialog_->SelectFile(
|
|
+ ui::SelectFileDialog::SELECT_OPEN_FILE,
|
|
+ std::u16string(),
|
|
+ export_path_,
|
|
+ &file_type_info,
|
|
+ 0,
|
|
+ base::FilePath::StringType(),
|
|
+ window
|
|
+ );
|
|
+}
|
|
+
|
|
+void BookmarkBridge::ExportBookmarks(JNIEnv* env,
|
|
+ const JavaRef<jobject>& obj,
|
|
+ const JavaRef<jobject>& java_window,
|
|
+ const JavaRef<jstring>& j_export_path) {
|
|
+ DCHECK(IsLoaded());
|
|
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
|
+
|
|
+ ui::WindowAndroid* window =
|
|
+ ui::WindowAndroid::FromJavaWindowAndroid(java_window);
|
|
+ CHECK(window);
|
|
+
|
|
+ std::u16string export_path =
|
|
+ base::android::ConvertJavaStringToUTF16(env, j_export_path);
|
|
+
|
|
+ export_path_ = base::FilePath::FromUTF16Unsafe(export_path);
|
|
+
|
|
+ if (export_path_.empty()) {
|
|
+ if (!base::android::GetDownloadsDirectory(&export_path_)) {
|
|
+ LOG(ERROR) << "Could not retrieve downloads directory for bookmarks export";
|
|
+ return;
|
|
+ }
|
|
+ export_path_ = export_path_.Append(FILE_PATH_LITERAL("bookmarks.html"));
|
|
+ }
|
|
+
|
|
+ bookmark_html_writer::WriteBookmarks(profile_, export_path_,
|
|
+ base::BindOnce(&BookmarkBridge::ExportBookmarksEnd,
|
|
+ weak_ptr_factory_.GetWeakPtr(),
|
|
+ base::android::ScopedJavaGlobalRef<jobject>(obj),
|
|
+ window));
|
|
+}
|
|
+
|
|
+void BookmarkBridge::ExportBookmarksEnd(const ScopedJavaGlobalRef<jobject>& obj,
|
|
+ ui::WindowAndroid* window,
|
|
+ bookmark_html_writer::Result result) const
|
|
+{
|
|
+ auto export_path = export_path_.MaybeAsASCII();
|
|
+ if (result == bookmark_html_writer::Result::kSuccess) {
|
|
+ LOG(INFO) << "Bookmarks exported successfully to " << export_path;
|
|
+ } else if (result == bookmark_html_writer::Result::kCouldNotCreateFile) {
|
|
+ LOG(ERROR) << "Bookmarks export: could not create file " << export_path;
|
|
+ } else if (result == bookmark_html_writer::Result::kCouldNotWriteHeader) {
|
|
+ LOG(ERROR) << "Bookmarks export: could not write header";
|
|
+ } else if (result == bookmark_html_writer::Result::kCouldNotWriteNodes) {
|
|
+ LOG(ERROR) << "Bookmarks export: could not write nodes";
|
|
+ }
|
|
+
|
|
+ JNIEnv* env = AttachCurrentThread();
|
|
+ Java_BookmarkBridge_bookmarksExported(env, obj, window->GetJavaObject(),
|
|
+ base::android::ConvertUTF8ToJavaString(env, export_path),
|
|
+ result == bookmark_html_writer::Result::kSuccess);
|
|
+}
|
|
+
|
|
+// Attempts to create a TemplateURL from the provided data. |title| is optional.
|
|
+// If TemplateURL creation fails, returns null.
|
|
+std::unique_ptr<TemplateURL> CreateTemplateURL(const std::u16string& url,
|
|
+ const std::u16string& keyword,
|
|
+ const std::u16string& title) {
|
|
+ if (url.empty() || keyword.empty())
|
|
+ return nullptr;
|
|
+ TemplateURLData data;
|
|
+ data.SetKeyword(keyword);
|
|
+ // We set short name by using the title if it exists.
|
|
+ // Otherwise, we use the shortcut.
|
|
+ data.SetShortName(title.empty() ? keyword : title);
|
|
+ data.SetURL(TemplateURLRef::DisplayURLToURLRef(url));
|
|
+ return std::make_unique<TemplateURL>(data);
|
|
+}
|
|
+
|
|
+void BookmarkBridge::FileSelected(const ui::SelectedFileInfo& file, int index) {
|
|
+ base::FilePath path = file.path();
|
|
+ base::ThreadPool::PostTaskAndReplyWithResult(
|
|
+ FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
|
|
+ base::BindOnce(&BookmarkBridge::FileSelectedImpl,
|
|
+ base::Unretained(this),
|
|
+ path),
|
|
+ base::BindOnce(&BookmarkBridge::FileSelectedImplOnUIThread,
|
|
+ base::Unretained(this),
|
|
+ path));
|
|
+}
|
|
+
|
|
+const std::string BookmarkBridge::FileSelectedImpl(const base::FilePath& path) {
|
|
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
|
|
+ if (!file.IsValid()) {
|
|
+ select_file_dialog_->ShowToast("Cannot open bookmarks file for import");
|
|
+ return "";
|
|
+ }
|
|
+
|
|
+ auto fileLength = file.GetLength();
|
|
+ if (-1 == fileLength) {
|
|
+ select_file_dialog_->ShowToast("Cannot read bookmarks file length");
|
|
+ return "";
|
|
+ }
|
|
+
|
|
+ std::vector<char> buffer(fileLength);
|
|
+ base::span<uint8_t> buffer_span = base::as_writable_byte_span(buffer);
|
|
+ if (-1 == file.ReadAtCurrentPos(buffer_span)) {
|
|
+ select_file_dialog_->ShowToast("Could not read bookmarks file");
|
|
+ return "";
|
|
+ }
|
|
+
|
|
+ if (buffer.empty()) {
|
|
+ select_file_dialog_->ShowToast("Empty bookmarks file");
|
|
+ return "";
|
|
+ }
|
|
+
|
|
+ std::string contents(buffer.begin(), buffer.end());
|
|
+ return contents;
|
|
+}
|
|
+
|
|
+void BookmarkBridge::FileSelectedImplOnUIThread(const base::FilePath& path,
|
|
+ const std::string& contents) {
|
|
+ if (contents.empty())
|
|
+ return;
|
|
+
|
|
+ // the following import logic comes from BookmarksFileImporter class
|
|
+ user_data_importer::BookmarkParser::ParsedBookmarks parsed_bookmarks =
|
|
+ user_data_importer::ParseBookmarksUnsafe(contents);
|
|
+
|
|
+ auto *writer = new ProfileWriter(profile_);
|
|
+
|
|
+ if (!parsed_bookmarks.bookmarks.empty()) {
|
|
+ // adding bookmarks will begin extensive changes to the model
|
|
+ writer->AddBookmarksWithModel(bookmark_model_, parsed_bookmarks.bookmarks, u"Imported");
|
|
+ }
|
|
+
|
|
+ std::stringstream message;
|
|
+ message << "Imported " << parsed_bookmarks.bookmarks.size() << " bookmarks " <<
|
|
+ " from " << path.MaybeAsASCII();
|
|
+ auto result = message.str();
|
|
+
|
|
+ select_file_dialog_->ShowToast(result);
|
|
+
|
|
+ LOG(INFO) << result;
|
|
+}
|
|
+
|
|
+void BookmarkBridge::FileSelectionCanceled() {}
|
|
+
|
|
void BookmarkBridge::SetBookmarkTitle(JNIEnv* env,
|
|
int64_t id,
|
|
int32_t type,
|
|
diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.h b/chrome/browser/bookmarks/android/bookmark_bridge.h
|
|
--- a/chrome/browser/bookmarks/android/bookmark_bridge.h
|
|
+++ b/chrome/browser/bookmarks/android/bookmark_bridge.h
|
|
@@ -19,6 +19,7 @@
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/supports_user_data.h"
|
|
#include "chrome/browser/partnerbookmarks/partner_bookmarks_shim.h"
|
|
+#include "chrome/browser/bookmarks/bookmark_html_writer.h"
|
|
#include "chrome/browser/profiles/profile.h"
|
|
#include "chrome/browser/profiles/profile_observer.h"
|
|
#include "chrome/browser/reading_list/android/reading_list_manager.h"
|
|
@@ -36,6 +37,9 @@
|
|
#include "components/signin/public/identity_manager/identity_manager.h"
|
|
#include "url/android/gurl_android.h"
|
|
|
|
+#include "components/search_engines/template_url.h"
|
|
+#include "ui/shell_dialogs/select_file_dialog.h"
|
|
+
|
|
class BookmarkBridgeTest;
|
|
|
|
// Values for a bitmask used to refer to a collection of bookmark nodes.
|
|
@@ -76,7 +80,8 @@ class BookmarkBridge : public ProfileObserver,
|
|
public reading_list::ReadingListManager::Observer,
|
|
public ReadingListModelObserver,
|
|
public signin::IdentityManager::Observer,
|
|
- public base::SupportsUserData::Data {
|
|
+ public base::SupportsUserData::Data,
|
|
+ public ui::SelectFileDialog::Listener {
|
|
public:
|
|
// All of the injected pointers must be non-null and must outlive `this`.
|
|
BookmarkBridge(Profile* profile,
|
|
@@ -107,6 +112,11 @@ class BookmarkBridge : public ProfileObserver,
|
|
|
|
bool IsDoingExtensiveChanges(JNIEnv* env);
|
|
|
|
+ // SelectFileDialog::Listener implementation.
|
|
+ void FileSelected(const ui::SelectedFileInfo& file,
|
|
+ int index) override;
|
|
+ void FileSelectionCanceled() override;
|
|
+
|
|
bool IsEditBookmarksEnabled(JNIEnv* env);
|
|
|
|
void LoadEmptyPartnerBookmarkShimForTesting(JNIEnv* env);
|
|
@@ -119,6 +129,17 @@ class BookmarkBridge : public ProfileObserver,
|
|
int64_t id,
|
|
int32_t type);
|
|
|
|
+ void ImportBookmarks(JNIEnv* env,
|
|
+ const base::android::JavaRef<jobject>& obj,
|
|
+ const base::android::JavaRef<jobject>& java_window);
|
|
+
|
|
+ void ExportBookmarks(JNIEnv* env,
|
|
+ const base::android::JavaRef<jobject>& obj,
|
|
+ const base::android::JavaRef<jobject>& java_window,
|
|
+ const base::android::JavaRef<jstring>& j_export_path);
|
|
+ void ExportBookmarksEnd(const base::android::ScopedJavaGlobalRef<jobject>& obj,
|
|
+ ui::WindowAndroid* window, bookmark_html_writer::Result result) const;
|
|
+
|
|
void GetAllFoldersWithDepths(
|
|
JNIEnv* env,
|
|
const base::android::JavaRef<jobject>& j_folders_obj,
|
|
@@ -372,6 +393,8 @@ class BookmarkBridge : public ProfileObserver,
|
|
void CreateOrDestroyAccountReadingListManagerIfNeeded();
|
|
|
|
const raw_ptr<Profile> profile_; // weak
|
|
+ base::FilePath export_path_;
|
|
+
|
|
base::android::ScopedJavaGlobalRef<jobject> java_bookmark_model_;
|
|
const raw_ptr<bookmarks::BookmarkModel> bookmark_model_; // weak
|
|
const raw_ptr<bookmarks::ManagedBookmarkService>
|
|
@@ -386,6 +409,7 @@ class BookmarkBridge : public ProfileObserver,
|
|
std::unique_ptr<bookmarks::ScopedGroupBookmarkActions>
|
|
grouped_bookmark_actions_;
|
|
PrefChangeRegistrar pref_change_registrar_;
|
|
+ scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
|
|
|
|
// Information about the Partner bookmarks (must check for IsLoaded()).
|
|
// This is owned by profile.
|
|
@@ -420,6 +444,10 @@ class BookmarkBridge : public ProfileObserver,
|
|
base::TimeTicks load_start_time_;
|
|
bool loading_notification_sent_ = false;
|
|
|
|
+ const std::string FileSelectedImpl(const base::FilePath& path);
|
|
+ void FileSelectedImplOnUIThread(const base::FilePath& path,
|
|
+ const std::string& contents);
|
|
+
|
|
// Weak pointers for creating callbacks that won't call into a destroyed
|
|
// object.
|
|
base::WeakPtrFactory<BookmarkBridge> weak_ptr_factory_;
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
|
|
@@ -36,6 +36,9 @@ import org.chromium.url.GURL;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
+import org.chromium.ui.base.WindowAndroid;
|
|
+import java.util.function.BiConsumer;
|
|
+
|
|
/**
|
|
* Provides the communication channel for Android to fetch and manipulate the bookmark model stored
|
|
* in native.
|
|
@@ -53,6 +56,9 @@ class BookmarkBridge {
|
|
private boolean mIsNativeBookmarkModelLoaded;
|
|
private boolean mInitializedPartnerBookmarks;
|
|
|
|
+ private static final BiConsumer<Boolean, String> NOOP = (success, bookmarksPath) -> {};
|
|
+ private BiConsumer<Boolean, String> mOnExportedFunction = NOOP;
|
|
+
|
|
// Lazily set pseudo-constants. These should never change at runtime. Used to avoid crossing
|
|
// JNI to fetch information.
|
|
private @Nullable BookmarkId mRootFolderId;
|
|
@@ -500,6 +506,35 @@ class BookmarkBridge {
|
|
.getTotalBookmarkCount(mNativeBookmarkBridge, id.getId(), id.getType());
|
|
}
|
|
|
|
+ /**
|
|
+ * Import bookmarks from a selected file.
|
|
+ * @param window The current window of the bookmarks activity or page.
|
|
+ */
|
|
+ public void importBookmarks(WindowAndroid window) {
|
|
+ assert mIsNativeBookmarkModelLoaded;
|
|
+ BookmarkBridgeJni.get().importBookmarks(mNativeBookmarkBridge,
|
|
+ BookmarkBridge.this, window);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Export bookmarks to a path selected by the user.
|
|
+ * @param window The current window of the bookmarks activity or page.
|
|
+ */
|
|
+ public void exportBookmarks(WindowAndroid window, String exportPath,
|
|
+ BiConsumer<Boolean, String> onExportedFunction) {
|
|
+ assert mIsNativeBookmarkModelLoaded;
|
|
+ mOnExportedFunction = onExportedFunction;
|
|
+ BookmarkBridgeJni.get().exportBookmarks(mNativeBookmarkBridge,
|
|
+ BookmarkBridge.this, window, exportPath);
|
|
+ }
|
|
+
|
|
+ @CalledByNative
|
|
+ public void bookmarksExported(WindowAndroid window, String bookmarksPath, boolean success) {
|
|
+ BiConsumer<Boolean, String> action = mOnExportedFunction
|
|
+ .andThen((x, y) -> mOnExportedFunction = NOOP);
|
|
+ action.accept(success, bookmarksPath);
|
|
+ }
|
|
+
|
|
/**
|
|
* Synchronously gets a list of bookmarks that match the specified search query.
|
|
*
|
|
@@ -1108,6 +1143,10 @@ class BookmarkBridge {
|
|
void getChildIds(
|
|
long nativeBookmarkBridge, long id, int type, List<BookmarkId> bookmarksList);
|
|
|
|
+ void importBookmarks(long nativeBookmarkBridge, BookmarkBridge caller, WindowAndroid window);
|
|
+ void exportBookmarks(long nativeBookmarkBridge, BookmarkBridge caller, WindowAndroid window,
|
|
+ String export_path);
|
|
+
|
|
BookmarkId getChildAt(long nativeBookmarkBridge, long id, int type, int index);
|
|
|
|
int getTotalBookmarkCount(long nativeBookmarkBridge, long id, int type);
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java
|
|
@@ -67,6 +67,16 @@ public interface BookmarkDelegate {
|
|
/** Shows the search UI. */
|
|
void openSearchUi();
|
|
|
|
+ /**
|
|
+ * Imports bookmarks from user-selected file.
|
|
+ */
|
|
+ void importBookmarks();
|
|
+
|
|
+ /**
|
|
+ * Exports bookmarks to downloads directory.
|
|
+ */
|
|
+ void exportBookmarks();
|
|
+
|
|
/** Add an observer to bookmark UI changes. */
|
|
void addUiObserver(BookmarkUiObserver observer);
|
|
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
|
|
@@ -66,6 +66,8 @@ import org.chromium.ui.base.ActivityResultTracker;
|
|
import org.chromium.ui.base.Clipboard;
|
|
import org.chromium.ui.base.WindowAndroid;
|
|
import org.chromium.ui.edge_to_edge.EdgeToEdgePadAdjuster;
|
|
+import org.chromium.ui.base.ActivityWindowAndroid;
|
|
+import org.chromium.ui.modaldialog.ModalDialogManager;
|
|
import org.chromium.ui.modaldialog.ModalDialogManager;
|
|
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
|
|
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
|
|
@@ -369,6 +371,13 @@ public class BookmarkManagerCoordinator
|
|
|
|
// Public API implementation.
|
|
|
|
+ /**
|
|
+ * Sets the Android window that is used by further intents created by the bookmark activity.
|
|
+ */
|
|
+ public void setWindow(ActivityWindowAndroid window, ModalDialogManager modalDialogManager) {
|
|
+ mMediator.setWindow(window, modalDialogManager);
|
|
+ }
|
|
+
|
|
/** Destroys and cleans up itself. This must be called after done using this class. */
|
|
public void onDestroyed() {
|
|
RecordUserAction.record("MobileBookmarkManagerClose");
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
|
|
@@ -68,6 +68,8 @@ import org.chromium.components.commerce.core.ShoppingService;
|
|
import org.chromium.components.commerce.core.SubscriptionsObserver;
|
|
import org.chromium.components.power_bookmarks.PowerBookmarkMeta;
|
|
import org.chromium.components.power_bookmarks.PowerBookmarkType;
|
|
+import org.chromium.ui.base.ActivityWindowAndroid;
|
|
+import org.chromium.ui.modaldialog.ModalDialogManager;
|
|
import org.chromium.ui.accessibility.AccessibilityState;
|
|
import org.chromium.ui.base.Clipboard;
|
|
import org.chromium.ui.base.DeviceFormFactor;
|
|
@@ -91,6 +93,47 @@ import java.util.function.BooleanSupplier;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Predicate;
|
|
|
|
+import android.content.Intent;
|
|
+import android.content.pm.PackageManager;
|
|
+import android.content.DialogInterface;
|
|
+import android.content.res.Resources;
|
|
+import android.content.ContentResolver;
|
|
+import android.net.Uri;
|
|
+import android.provider.Browser;
|
|
+import android.provider.DocumentsContract;
|
|
+import android.Manifest.permission;
|
|
+import android.view.View;
|
|
+import android.view.LayoutInflater;
|
|
+
|
|
+import androidx.appcompat.app.AlertDialog;
|
|
+import android.os.Build;
|
|
+
|
|
+import java.io.File;
|
|
+
|
|
+import org.chromium.base.ContextUtils;
|
|
+import org.chromium.base.ContentUriUtils;
|
|
+import org.chromium.base.shared_preferences.SharedPreferencesManager;
|
|
+import org.chromium.base.task.AsyncTask;
|
|
+import org.chromium.chrome.R;
|
|
+import org.chromium.chrome.browser.document.ChromeLauncherActivity;
|
|
+import org.chromium.chrome.browser.IntentHandler;
|
|
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
|
|
+import org.chromium.chrome.browser.download.DownloadLocationDialogType;
|
|
+import org.chromium.chrome.browser.download.settings.DownloadLocationHelperImpl;
|
|
+import org.chromium.chrome.browser.download.dialogs.DownloadLocationDialogController;
|
|
+import org.chromium.chrome.browser.download.dialogs.DownloadLocationDialogCoordinator;
|
|
+import org.chromium.chrome.browser.download.dialogs.DownloadLocationCustomView;
|
|
+import org.chromium.chrome.browser.download.DirectoryOption;
|
|
+import org.chromium.chrome.browser.profiles.ProfileManager;
|
|
+import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
|
|
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
|
|
+import org.chromium.ui.base.PageTransition;
|
|
+import org.chromium.ui.base.WindowAndroid;
|
|
+import org.chromium.ui.modelutil.PropertyModel;
|
|
+import org.chromium.ui.modaldialog.ModalDialogManager;
|
|
+import org.chromium.ui.modaldialog.ModalDialogProperties;
|
|
+import org.chromium.ui.modaldialog.DialogDismissalCause;
|
|
+
|
|
/** Responsible for BookmarkManager business logic. */
|
|
// TODO(crbug.com/40256938): Remove BookmarkDelegate if possible.
|
|
@NullMarked
|
|
@@ -101,6 +144,9 @@ class BookmarkManagerMediator
|
|
|
|
private static boolean sPreventLoadingForTesting;
|
|
|
|
+ private ActivityWindowAndroid mWindowAndroid;
|
|
+ private ModalDialogManager mModalDialogManager;
|
|
+
|
|
/** Keeps track of whether drag is enabled / active for bookmark lists. */
|
|
private class BookmarkDragStateDelegate implements DragStateDelegate {
|
|
private BookmarkDelegate mBookmarkDelegate;
|
|
@@ -651,6 +697,14 @@ class BookmarkManagerMediator
|
|
mNativePage = nativePage;
|
|
}
|
|
|
|
+ /**
|
|
+ * Sets the Android window that is used by further intents created by the bookmark activity.
|
|
+ */
|
|
+ public void setWindow(ActivityWindowAndroid window, ModalDialogManager modalDialogManager) {
|
|
+ mWindowAndroid = window;
|
|
+ mModalDialogManager = modalDialogManager;
|
|
+ }
|
|
+
|
|
/** See BookmarkManager(Coordinator)#updateForUrl */
|
|
void updateForUrl(String url) {
|
|
// Bookmark model is null if the manager has been destroyed.
|
|
@@ -819,6 +873,235 @@ class BookmarkManagerMediator
|
|
}
|
|
}
|
|
|
|
+ @Override
|
|
+ public void importBookmarks() {
|
|
+ mBookmarkModel.importBookmarks(mWindowAndroid);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void exportBookmarks() {
|
|
+ if (ChromeFeatureList.isEnabled(ChromeFeatureList.BOOKMARKS_EXPORT_USESAF) ||
|
|
+ Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
|
+ exportBookmarksImplUseSaf();
|
|
+ } else {
|
|
+ exportBookmarksImplUseFile();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void exportBookmarksImplUseSaf() {
|
|
+ Context context = mWindowAndroid.getContext().get();
|
|
+
|
|
+ // standard name for boorkmark file
|
|
+ final String standardBoorkmarkName = "bookmarks.html";
|
|
+
|
|
+ // use the fileSelector and saf asking user for the file
|
|
+ Intent fileSelector = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
|
+ fileSelector.addCategory(Intent.CATEGORY_OPENABLE);
|
|
+ fileSelector.setType("text/html");
|
|
+ fileSelector.putExtra(Intent.EXTRA_TITLE, standardBoorkmarkName);
|
|
+ fileSelector.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
|
+ Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
|
+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
|
+
|
|
+ // get last exported uri path, if any
|
|
+ SharedPreferencesManager sharedPrefs = ChromeSharedPreferences.getInstance();
|
|
+ String bookmarksPath = sharedPrefs.readString(ChromePreferenceKeys.BOOKMARKS_LAST_EXPORT_URI, standardBoorkmarkName);
|
|
+ Uri lastSelectedUri = Uri.parse(bookmarksPath);
|
|
+
|
|
+ // prepare delegate for file selector
|
|
+ DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
|
|
+ @Override
|
|
+ public void onClick(DialogInterface dialog, int button) {
|
|
+ if (button == AlertDialog.BUTTON_NEGATIVE) {
|
|
+ mWindowAndroid.showIntent(fileSelector,
|
|
+ new WindowAndroid.IntentCallback() {
|
|
+ @Override
|
|
+ public void onIntentCompleted(int resultCode, Intent data) {
|
|
+ if (data == null) return;
|
|
+ Uri filePath = data.getData();
|
|
+ doExportBookmarksImpl(filePath);
|
|
+ }
|
|
+ },
|
|
+ null);
|
|
+ } else {
|
|
+ if (dialog!=null) dialog.dismiss();
|
|
+ doExportBookmarksImpl(lastSelectedUri);
|
|
+ }
|
|
+ }
|
|
+ };
|
|
+
|
|
+ // as a workaround for https://issuetracker.google.com/issues/37136466
|
|
+ // ask to overwrite if is a valid uri and the file is present
|
|
+ if (DocumentsContract.isDocumentUri(context, lastSelectedUri)) {
|
|
+ AsyncTask<Void> checkUriTask = new AsyncTask<Void>() {
|
|
+ boolean uriExists = false;
|
|
+ String actualFilePath = null;
|
|
+
|
|
+ @Override
|
|
+ protected Void doInBackground() {
|
|
+ uriExists = ContentUriUtils.contentUriExists(lastSelectedUri.toString());
|
|
+ if (uriExists) {
|
|
+ actualFilePath = ContentUriUtils.getFilePathFromContentUri(lastSelectedUri);
|
|
+ // get real actual file name on disk
|
|
+ if (actualFilePath==null) actualFilePath = lastSelectedUri.toString();
|
|
+ // set file name to last exported file name
|
|
+ fileSelector.putExtra(Intent.EXTRA_TITLE,
|
|
+ ContentUriUtils.getDisplayName(lastSelectedUri, context,
|
|
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void onPostExecute(Void result) {
|
|
+ // check for permissions
|
|
+ if (uriExists) {
|
|
+ AlertDialog.Builder alert =
|
|
+ new AlertDialog.Builder(context, R.style.ThemeOverlay_BrowserUI_AlertDialog);
|
|
+ AlertDialog alertDialog =
|
|
+ alert.setTitle(R.string.export_bookmarks_alert_title)
|
|
+ .setMessage(context.getString(R.string.export_bookmarks_alert_message, actualFilePath))
|
|
+ .setPositiveButton(
|
|
+ R.string.export_bookmarks_alert_message_yes, onClickListener)
|
|
+ .setNegativeButton(R.string.export_bookmarks_alert_message_no, onClickListener)
|
|
+ .create();
|
|
+ alertDialog.getDelegate().setHandleNativeActionModesEnabled(false);
|
|
+
|
|
+ // show dialog asking for overwrite
|
|
+ alertDialog.show();
|
|
+ return;
|
|
+ } else {
|
|
+ onClickListener.onClick(null, AlertDialog.BUTTON_NEGATIVE);
|
|
+ }
|
|
+ }
|
|
+ };
|
|
+ checkUriTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // actually open the file selector
|
|
+ onClickListener.onClick(null, AlertDialog.BUTTON_NEGATIVE);
|
|
+ }
|
|
+
|
|
+ private void exportBookmarksImplUseFile() {
|
|
+ Context context = mWindowAndroid.getContext().get();
|
|
+
|
|
+ // standard name for boorkmark file
|
|
+ final String standardBoorkmarkName = "bookmarks.html";
|
|
+
|
|
+ // use the download ui and standard file saving
|
|
+ DownloadLocationDialogController controller = new DownloadLocationDialogController() {
|
|
+ @Override
|
|
+ public void onDownloadLocationDialogComplete(String returnedPath, boolean didUserConfirm) {}
|
|
+
|
|
+ @Override
|
|
+ public void onDownloadLocationDialogCanceled() {}
|
|
+ };
|
|
+
|
|
+ DownloadLocationDialogCoordinator dialog = new DownloadLocationDialogCoordinator() {
|
|
+ @Override
|
|
+ protected void onDirectoryOptionsRetrieved(ArrayList<DirectoryOption> dirs) {
|
|
+ if (mDialogModel != null) return;
|
|
+
|
|
+ // Actually show the dialog.
|
|
+ mCustomView = (DownloadLocationCustomView) LayoutInflater.from(context).inflate(
|
|
+ R.layout.download_location_dialog, null);
|
|
+ mCustomView.initialize(DownloadLocationDialogType.DEFAULT, /*totalBytes*/ 0,
|
|
+ (isChecked) -> {},
|
|
+ new DownloadLocationHelperImpl(mProfile));
|
|
+ mCustomView.setTitle(context.getString(R.string.export_bookmarks_alert_title));
|
|
+ mCustomView.setFileName(standardBoorkmarkName);
|
|
+ mCustomView.mDontShowAgain.setVisibility(View.GONE);
|
|
+
|
|
+ Resources resources = context.getResources();
|
|
+ mDialogModel = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
|
|
+ .with(ModalDialogProperties.CONTROLLER, this)
|
|
+ .with(ModalDialogProperties.CUSTOM_VIEW, mCustomView)
|
|
+ .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, resources,
|
|
+ R.string.export_bookmarks)
|
|
+ .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources,
|
|
+ R.string.cancel)
|
|
+ .build();
|
|
+
|
|
+ mModalDialogManager.showDialog(mDialogModel, ModalDialogManager.ModalDialogType.APP);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onDismiss(PropertyModel model, int dismissalCause) {
|
|
+ switch (dismissalCause) {
|
|
+ case DialogDismissalCause.POSITIVE_BUTTON_CLICKED:
|
|
+ {
|
|
+ String fileName = mCustomView.getFileName();
|
|
+ String directory = mCustomView.getDirectoryOption().location;
|
|
+ if (fileName != null && directory != null) {
|
|
+ File file = new File(directory, fileName);
|
|
+
|
|
+ if (mWindowAndroid.hasPermission(permission.WRITE_EXTERNAL_STORAGE)) {
|
|
+ doExportBookmarksImpl(file.getPath());
|
|
+ } else {
|
|
+ String[] requestPermissions = new String[] {permission.WRITE_EXTERNAL_STORAGE};
|
|
+ mWindowAndroid.requestPermissions(requestPermissions, (permissions, grantResults) -> {
|
|
+ if (grantResults.length >= 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
+ doExportBookmarksImpl(file.getPath());
|
|
+ }
|
|
+ });
|
|
+ };
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ mDialogModel = null;
|
|
+ mCustomView = null;
|
|
+ }
|
|
+ };
|
|
+ dialog.initialize(controller);
|
|
+ dialog.showDialog(context, mModalDialogManager, /*totalBytes*/ 0,
|
|
+ DownloadLocationDialogType.DEFAULT, /*suggestedPath*/ "",
|
|
+ ProfileManager.getLastUsedRegularProfile());
|
|
+ }
|
|
+
|
|
+ private void doExportBookmarksImpl(Uri filePath) {
|
|
+ ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver();
|
|
+ // since we want to persist the uri in settings, ask for persistable permissions
|
|
+ resolver.takePersistableUriPermission(filePath, Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
|
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
+
|
|
+ doExportBookmarksImpl(filePath.toString());
|
|
+ }
|
|
+
|
|
+ private void doExportBookmarksImpl(String filePath) {
|
|
+ mBookmarkModel.exportBookmarks(mWindowAndroid, filePath,
|
|
+ (success, bookmarksPath) -> {
|
|
+ if (!success) {
|
|
+ ((Activity)mWindowAndroid.getContext().get()).runOnUiThread(new Runnable() {
|
|
+ public void run() {
|
|
+ mWindowAndroid.showError(R.string.saving_file_error);
|
|
+ }
|
|
+ });
|
|
+ } else {
|
|
+ SharedPreferencesManager sharedPrefs = ChromeSharedPreferences.getInstance();
|
|
+ sharedPrefs.writeString(ChromePreferenceKeys.BOOKMARKS_LAST_EXPORT_URI, bookmarksPath);
|
|
+
|
|
+ Context context = ContextUtils.getApplicationContext();
|
|
+
|
|
+ Intent intent = new Intent(Intent.ACTION_VIEW,
|
|
+ ContentUriUtils.isContentUri(bookmarksPath) ?
|
|
+ Uri.parse(bookmarksPath) : Uri.parse("file://" + bookmarksPath));
|
|
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID,
|
|
+ context.getPackageName());
|
|
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
+ intent.putExtra(IntentHandler.EXTRA_PAGE_TRANSITION_TYPE, PageTransition.AUTO_BOOKMARK);
|
|
+
|
|
+ // If the bookmark manager is shown in a tab on a phone (rather than in a separate
|
|
+ // activity) the component name may be null. Send the intent through
|
|
+ // ChromeLauncherActivity instead to avoid crashing. See crbug.com/615012.
|
|
+ intent.setClass(context, ChromeLauncherActivity.class);
|
|
+
|
|
+ IntentHandler.startActivityForTrustedIntent(intent);
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
@Override
|
|
public void openSearchUi() {
|
|
onSearchTextChangeCallback("");
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java
|
|
@@ -22,6 +22,9 @@ import org.chromium.ui.base.ActivityResultTracker;
|
|
import org.chromium.ui.base.WindowAndroid;
|
|
|
|
import java.util.function.Supplier;
|
|
+import org.chromium.chrome.browser.app.ChromeActivity;
|
|
+import org.chromium.ui.modaldialog.ModalDialogManager;
|
|
+import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
|
|
|
|
/** A native page holding a {@link BookmarkManagerCoordinator} on _tablet_. */
|
|
@NullMarked
|
|
@@ -48,7 +51,7 @@ public class BookmarkPage extends BasicNativePage {
|
|
*/
|
|
public BookmarkPage(
|
|
WindowAndroid windowAndroid,
|
|
- Activity activity,
|
|
+ ChromeActivity activity,
|
|
SnackbarManager snackbarManager,
|
|
Supplier<BottomSheetController> bottomSheetControllerSupplier,
|
|
ActivityResultTracker activityResultTracker,
|
|
@@ -87,6 +90,9 @@ public class BookmarkPage extends BasicNativePage {
|
|
backPressManager);
|
|
|
|
mBookmarkManagerCoordinator.setBasicNativePage(this);
|
|
+ mBookmarkManagerCoordinator.setWindow(activity.getWindowAndroid(),
|
|
+ new ModalDialogManager(
|
|
+ new AppModalPresenter(activity), ModalDialogManager.ModalDialogType.APP));
|
|
initWithView(mBookmarkManagerCoordinator.getView());
|
|
|
|
setBackPressHandler(mBookmarkManagerCoordinator, backPressManager);
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java
|
|
@@ -103,6 +103,17 @@ public class BookmarkToolbar extends SelectableListToolbar<BookmarkId>
|
|
setOnMenuItemClickListener(dragEnabled ? null : this);
|
|
}
|
|
|
|
+ private Runnable mImportBookmarkRunnable;
|
|
+ private Runnable mExportBookmarkRunnable;
|
|
+
|
|
+ void setImportBookmarkRunnable(Runnable runnable) {
|
|
+ mImportBookmarkRunnable = runnable;
|
|
+ }
|
|
+
|
|
+ void setExportBookmarkRunnable(Runnable runnable) {
|
|
+ mExportBookmarkRunnable = runnable;
|
|
+ }
|
|
+
|
|
void setEditButtonVisible(boolean visible) {
|
|
mEditButtonVisible = visible;
|
|
getMenu().findItem(R.id.edit_menu_id).setVisible(visible);
|
|
@@ -185,6 +196,12 @@ public class BookmarkToolbar extends SelectableListToolbar<BookmarkId>
|
|
|
|
void setCurrentFolder(BookmarkId folder) {
|
|
mCurrentFolder = mBookmarkModel.getBookmarkById(folder);
|
|
+ enableImportExportMenu();
|
|
+ }
|
|
+
|
|
+ void enableImportExportMenu() {
|
|
+ getMenu().findItem(R.id.import_menu_id).setVisible(true);
|
|
+ getMenu().findItem(R.id.export_menu_id).setVisible(true);
|
|
}
|
|
|
|
void setNavigateBackRunnable(Runnable navigateBackRunnable) {
|
|
@@ -209,6 +226,13 @@ public class BookmarkToolbar extends SelectableListToolbar<BookmarkId>
|
|
@Override
|
|
public boolean onMenuItemClick(MenuItem menuItem) {
|
|
hideOverflowMenu();
|
|
+ if (menuItem.getItemId() == R.id.import_menu_id) {
|
|
+ mImportBookmarkRunnable.run();
|
|
+ return true;
|
|
+ } else if (menuItem.getItemId() == R.id.export_menu_id) {
|
|
+ mExportBookmarkRunnable.run();
|
|
+ return true;
|
|
+ }
|
|
return assumeNonNull(mMenuIdClickedFunction).apply(menuItem.getItemId());
|
|
}
|
|
|
|
@@ -229,6 +253,9 @@ public class BookmarkToolbar extends SelectableListToolbar<BookmarkId>
|
|
protected void showNormalView() {
|
|
super.showNormalView();
|
|
|
|
+ getMenu().findItem(R.id.import_menu_id).setVisible(mCurrentFolder != null);
|
|
+ getMenu().findItem(R.id.export_menu_id).setVisible(mCurrentFolder != null);
|
|
+
|
|
// SelectableListToolbar will show/hide the entire group.
|
|
setEditButtonVisible(mEditButtonVisible);
|
|
setNewFolderButtonVisible(mNewFolderButtonVisible);
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java
|
|
@@ -148,6 +148,10 @@ class BookmarkToolbarMediator
|
|
mBookmarkDelegate = bookmarkDelegate;
|
|
mModel.set(
|
|
BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE, this::onNavigateBack);
|
|
+ mModel.set(
|
|
+ BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE, mBookmarkDelegate::importBookmarks);
|
|
+ mModel.set(
|
|
+ BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE, mBookmarkDelegate::exportBookmarks);
|
|
mBookmarkDelegate.addUiObserver(this);
|
|
mBookmarkDelegate.notifyStateChange(this);
|
|
});
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java
|
|
@@ -88,6 +88,10 @@ class BookmarkToolbarProperties {
|
|
|
|
static final WritableObjectPropertyKey<View> NEXT_FOCUSABLE_VIEW =
|
|
new WritableObjectPropertyKey<>();
|
|
+ static final WritableObjectPropertyKey<Runnable> IMPORT_BOOKMARK_RUNNABLE =
|
|
+ new WritableObjectPropertyKey<>();
|
|
+ static final WritableObjectPropertyKey<Runnable> EXPORT_BOOKMARK_RUNNABLE =
|
|
+ new WritableObjectPropertyKey<>();
|
|
|
|
static final PropertyKey[] ALL_KEYS = {
|
|
SELECTION_DELEGATE,
|
|
@@ -117,6 +121,8 @@ class BookmarkToolbarProperties {
|
|
SELECTION_MODE_SHOW_COPY_LINK,
|
|
SELECTION_MODE_SHOW_MARK_READ,
|
|
SELECTION_MODE_SHOW_MARK_UNREAD,
|
|
- NEXT_FOCUSABLE_VIEW
|
|
+ NEXT_FOCUSABLE_VIEW,
|
|
+ IMPORT_BOOKMARK_RUNNABLE,
|
|
+ EXPORT_BOOKMARK_RUNNABLE
|
|
};
|
|
}
|
|
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java
|
|
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java
|
|
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java
|
|
@@ -56,6 +56,12 @@ class BookmarkToolbarViewBinder {
|
|
model.get(BookmarkToolbarProperties.CHECKED_VIEW_MENU_ID));
|
|
} else if (key == BookmarkToolbarProperties.CURRENT_FOLDER) {
|
|
bookmarkToolbar.setCurrentFolder(model.get(BookmarkToolbarProperties.CURRENT_FOLDER));
|
|
+ } else if (key == BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE) {
|
|
+ bookmarkToolbar.setImportBookmarkRunnable(
|
|
+ model.get(BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE));
|
|
+ } else if (key == BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE) {
|
|
+ bookmarkToolbar.setExportBookmarkRunnable(
|
|
+ model.get(BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE));
|
|
} else if (key == BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE) {
|
|
bookmarkToolbar.setNavigateBackRunnable(
|
|
model.get(BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE));
|
|
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
|
|
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
|
|
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java
|
|
@@ -50,7 +50,7 @@ public class DownloadLocationCustomView extends ScrollView
|
|
private TextView mFileSize;
|
|
private Spinner mFileLocation;
|
|
private TextView mLocationAvailableSpace;
|
|
- private CheckBox mDontShowAgain;
|
|
+ public CheckBox mDontShowAgain;
|
|
private @DownloadLocationDialogType int mDialogType;
|
|
private long mTotalBytes;
|
|
private @Nullable Callback<Boolean> mOnClickedCallback;
|
|
@@ -75,7 +75,7 @@ public class DownloadLocationCustomView extends ScrollView
|
|
mDontShowAgain = findViewById(R.id.show_again_checkbox);
|
|
}
|
|
|
|
- void initialize(
|
|
+ public void initialize(
|
|
@DownloadLocationDialogType int dialogType,
|
|
long totalBytes,
|
|
Callback<Boolean> onClickedCallback,
|
|
@@ -133,7 +133,7 @@ public class DownloadLocationCustomView extends ScrollView
|
|
/**
|
|
* @return The text that the user inputted as the name of the file.
|
|
*/
|
|
- @Nullable String getFileName() {
|
|
+ @Nullable public String getFileName() {
|
|
if (mFileName == null || mFileName.getText() == null) return null;
|
|
return mFileName.getText().toString();
|
|
}
|
|
@@ -141,7 +141,7 @@ public class DownloadLocationCustomView extends ScrollView
|
|
/**
|
|
* @return The file path based on what the user selected as the location of the file.
|
|
*/
|
|
- @Nullable DirectoryOption getDirectoryOption() {
|
|
+ @Nullable public DirectoryOption getDirectoryOption() {
|
|
if (mFileLocation == null) return null;
|
|
DirectoryOption selected = (DirectoryOption) mFileLocation.getSelectedItem();
|
|
return selected;
|
|
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogCoordinator.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogCoordinator.java
|
|
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogCoordinator.java
|
|
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogCoordinator.java
|
|
@@ -43,13 +43,13 @@ import java.util.ArrayList;
|
|
@NullMarked
|
|
public class DownloadLocationDialogCoordinator implements ModalDialogProperties.Controller {
|
|
private DownloadLocationDialogController mController;
|
|
- private @Nullable PropertyModel mDialogModel;
|
|
+ protected @Nullable PropertyModel mDialogModel;
|
|
private @Nullable PropertyModel mDownloadLocationDialogModel;
|
|
private @Nullable
|
|
PropertyModelChangeProcessor<PropertyModel, DownloadLocationCustomView, PropertyKey>
|
|
mPropertyModelChangeProcessor;
|
|
- private @Nullable DownloadLocationCustomView mCustomView;
|
|
- private @Nullable ModalDialogManager mModalDialogManager;
|
|
+ protected @Nullable DownloadLocationCustomView mCustomView;
|
|
+ protected @Nullable ModalDialogManager mModalDialogManager;
|
|
|
|
private long mTotalBytes;
|
|
private @DownloadLocationDialogType int mDialogType;
|
|
@@ -57,7 +57,7 @@ public class DownloadLocationDialogCoordinator implements ModalDialogProperties.
|
|
private @Nullable Context mContext;
|
|
|
|
private boolean mHasMultipleDownloadLocations;
|
|
- private @Nullable Profile mProfile;
|
|
+ protected @Nullable Profile mProfile;
|
|
|
|
private boolean mLocationDialogManaged;
|
|
|
|
@@ -154,7 +154,7 @@ public class DownloadLocationDialogCoordinator implements ModalDialogProperties.
|
|
* Called after retrieved the download directory options.
|
|
* @param dirs An list of available download directories.
|
|
*/
|
|
- private void onDirectoryOptionsRetrieved(ArrayList<DirectoryOption> dirs) {
|
|
+ protected void onDirectoryOptionsRetrieved(ArrayList<DirectoryOption> dirs) {
|
|
assertNonNull(mContext);
|
|
assertNonNull(mModalDialogManager);
|
|
assertNonNull(mSuggestedPath);
|
|
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
|
|
--- a/chrome/browser/flags/android/chrome_feature_list.cc
|
|
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
|
|
@@ -279,6 +279,7 @@ const base::Feature* const kFeaturesExposedToJava[] = {
|
|
&kBlockIntentsWhileLocked,
|
|
&kBookmarkPaneAndroid,
|
|
&kBottomSheetAsBrowserControls,
|
|
+ &kBookmarksExportUseSaf,
|
|
&kBrowserControlsDebugging,
|
|
&kBrowserControlsEarlyResize,
|
|
&kBrowserControlsPersistsOnCvh,
|
|
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
|
|
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
|
|
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
|
|
@@ -724,6 +724,7 @@ public abstract class ChromeFeatureList {
|
|
"UseLibunwindstackNativeUnwinderAndroid";
|
|
public static final String VERIFY_QWACS = "VerifyQWACs";
|
|
public static final String VISITED_URL_RANKING_SERVICE = "VisitedURLRankingService";
|
|
+ public static final String BOOKMARKS_EXPORT_USESAF = "BookmarksExportUseSaf";
|
|
public static final String WEB_APK_BACKUP_AND_RESTORE_BACKEND = "WebApkBackupAndRestoreBackend";
|
|
public static final String WEB_APK_INSTALL_FAILURE_NOTIFICATION =
|
|
"WebApkInstallFailureNotification";
|
|
diff --git a/chrome/browser/importer/profile_writer.cc b/chrome/browser/importer/profile_writer.cc
|
|
--- a/chrome/browser/importer/profile_writer.cc
|
|
+++ b/chrome/browser/importer/profile_writer.cc
|
|
@@ -126,6 +126,16 @@ void ProfileWriter::AddBookmarks(
|
|
return;
|
|
|
|
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile_);
|
|
+ AddBookmarksWithModel(model, bookmarks, top_level_folder_name);
|
|
+}
|
|
+
|
|
+void ProfileWriter::AddBookmarksWithModel(
|
|
+ BookmarkModel* model,
|
|
+ const std::vector<user_data_importer::ImportedBookmarkEntry>& bookmarks,
|
|
+ const std::u16string& top_level_folder_name) {
|
|
+ if (bookmarks.empty())
|
|
+ return;
|
|
+
|
|
DCHECK(model->loaded());
|
|
|
|
// If the bookmark bar is currently empty, we should import directly to it.
|
|
diff --git a/chrome/browser/importer/profile_writer.h b/chrome/browser/importer/profile_writer.h
|
|
--- a/chrome/browser/importer/profile_writer.h
|
|
+++ b/chrome/browser/importer/profile_writer.h
|
|
@@ -11,6 +11,7 @@
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/memory/ref_counted.h"
|
|
#include "build/build_config.h"
|
|
+#include "components/bookmarks/browser/bookmark_model.h"
|
|
#include "components/favicon_base/favicon_usage_data.h"
|
|
#include "components/history/core/browser/history_types.h"
|
|
#include "components/search_engines/template_url_service.h"
|
|
@@ -75,6 +76,11 @@ class ProfileWriter : public base::RefCountedThreadSafe<ProfileWriter> {
|
|
const std::vector<user_data_importer::ImportedBookmarkEntry>& bookmarks,
|
|
const std::u16string& top_level_folder_name);
|
|
|
|
+ virtual void AddBookmarksWithModel(
|
|
+ bookmarks::BookmarkModel* model,
|
|
+ const std::vector<user_data_importer::ImportedBookmarkEntry>& bookmarks,
|
|
+ const std::u16string& top_level_folder_name);
|
|
+
|
|
virtual void AddFavicons(const favicon_base::FaviconUsageDataList& favicons);
|
|
|
|
// Adds the TemplateURLs in |template_urls| to the local store.
|
|
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
|
|
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
|
|
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
|
|
@@ -140,6 +140,8 @@ public final class ChromePreferenceKeys {
|
|
"enhanced_bookmark_last_used_parent_folder";
|
|
public static final String BOOKMARKS_SORT_ORDER = "Chrome.Bookmarks.BookmarkRowSortOrder";
|
|
public static final String BOOKMARKS_VISUALS_PREF = "Chrome.Bookmarks.BookmarkRowDisplay";
|
|
+ public static final String BOOKMARKS_LAST_EXPORT_URI =
|
|
+ "Chrome.Bookmarks.Last_Export_Uri";
|
|
|
|
/** Whether Chrome is set as the default browser. Default value is false. */
|
|
public static final String CHROME_DEFAULT_BROWSER = "applink.chrome_default_browser";
|
|
@@ -1131,6 +1133,7 @@ public final class ChromePreferenceKeys {
|
|
AUXILIARY_SEARCH_SCHEMA_VERSION,
|
|
APP_LAUNCH_LAST_KNOWN_ACTIVE_TAB_STATE,
|
|
APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO,
|
|
+ BOOKMARKS_LAST_EXPORT_URI,
|
|
APPLICATION_OVERRIDE_LANGUAGE,
|
|
BLUETOOTH_NOTIFICATION_IDS,
|
|
BOOKMARKS_SORT_ORDER,
|
|
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
|
|
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
|
|
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
|
|
@@ -277,6 +277,24 @@ CHAR_LIMIT guidelines:
|
|
<message name="IDS_NOTIFICATION_CATEGORY_SITES" desc="Label for notifications from websites, within a list of notification categories. [CHAR_LIMIT=32]">
|
|
Sites
|
|
</message>
|
|
+ <message name="IDS_IMPORT_BOOKMARKS" desc="The label for the import bookmarks button.">
|
|
+ Import
|
|
+ </message>
|
|
+ <message name="IDS_EXPORT_BOOKMARKS" desc="The label for an export bookmarks button.">
|
|
+ Export
|
|
+ </message>
|
|
+ <message name="IDS_EXPORT_BOOKMARKS_ALERT_TITLE" desc="The message for the alert title asking to overwrite the file.">
|
|
+ Export bookmarks to file
|
|
+ </message>
|
|
+ <message name="IDS_EXPORT_BOOKMARKS_ALERT_MESSAGE" desc="The message asking user to overwrite the file.">
|
|
+ Do you want to overwrite <ph name="APPLICATION">%s</ph>?
|
|
+ </message>
|
|
+ <message name="IDS_EXPORT_BOOKMARKS_ALERT_MESSAGE_YES" desc="Label for yes button.">
|
|
+ Yes
|
|
+ </message>
|
|
+ <message name="IDS_EXPORT_BOOKMARKS_ALERT_MESSAGE_NO" desc="Label for no button.">
|
|
+ Choose another file
|
|
+ </message>
|
|
<message name="IDS_NOTIFICATION_CATEGORY_VR" desc="Label for notifications in VR, within a list of notification categories. [CHAR_LIMIT=32]">
|
|
Virtual Reality
|
|
</message>
|
|
diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn
|
|
--- a/chrome/utility/BUILD.gn
|
|
+++ b/chrome/utility/BUILD.gn
|
|
@@ -215,6 +215,13 @@ static_library("utility") {
|
|
deps += [ "//chrome/services/pdf:lib" ]
|
|
}
|
|
|
|
+ if (is_android) {
|
|
+ sources += [
|
|
+ "importer/bookmarks_file_importer.cc",
|
|
+ "importer/bookmarks_file_importer.h",
|
|
+ ]
|
|
+ }
|
|
+
|
|
# NSS decryptor is not needed on ChromeOS.
|
|
if (!is_chromeos && use_nss_certs) {
|
|
sources += [
|
|
diff --git a/chrome/utility/importer/bookmarks_file_importer.cc b/chrome/utility/importer/bookmarks_file_importer.cc
|
|
--- a/chrome/utility/importer/bookmarks_file_importer.cc
|
|
+++ b/chrome/utility/importer/bookmarks_file_importer.cc
|
|
@@ -103,8 +103,12 @@ void BookmarksFileImporter::StartImport(
|
|
user_data_importer::ParseBookmarksUnsafe(raw_html);
|
|
|
|
if (!parsed_bookmarks.bookmarks.empty()) {
|
|
+#if BUILDFLAG(IS_ANDROID)
|
|
+ std::u16string first_folder_name;
|
|
+#else
|
|
std::u16string first_folder_name =
|
|
bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP);
|
|
+#endif
|
|
std::erase_if(parsed_bookmarks.bookmarks,
|
|
[](user_data_importer::ImportedBookmarkEntry bookmark) {
|
|
return !internal::CanImportURL(bookmark.url);
|
|
diff --git a/components/headless/select_file_dialog/headless_select_file_dialog.cc b/components/headless/select_file_dialog/headless_select_file_dialog.cc
|
|
--- a/components/headless/select_file_dialog/headless_select_file_dialog.cc
|
|
+++ b/components/headless/select_file_dialog/headless_select_file_dialog.cc
|
|
@@ -57,6 +57,10 @@ class HeadlessSelectFileDialog : public ui::SelectFileDialog {
|
|
// ui::SelectFileDialog:
|
|
bool HasMultipleFileTypeChoicesImpl() override { return false; }
|
|
|
|
+ void ShowToast(const std::string& message) override {
|
|
+ // nothing to do, used only on android
|
|
+ }
|
|
+
|
|
SelectFileDialogCallback callback_;
|
|
};
|
|
|
|
diff --git a/components/user_data_importer/content/content_bookmark_parser_utils.cc b/components/user_data_importer/content/content_bookmark_parser_utils.cc
|
|
--- a/components/user_data_importer/content/content_bookmark_parser_utils.cc
|
|
+++ b/components/user_data_importer/content/content_bookmark_parser_utils.cc
|
|
@@ -138,6 +138,7 @@ std::optional<bool> GetBoolAttribute(std::string_view attribute_list,
|
|
|
|
// Given the URL of a page and a favicon data URL, adds an appropriate record
|
|
// to the given favicon usage vector.
|
|
+[[maybe_unused]]
|
|
void DataURLToFaviconUsage(const GURL& link_url,
|
|
const GURL& favicon_data,
|
|
favicon_base::FaviconUsageDataList* favicons) {
|
|
@@ -495,9 +496,11 @@ BookmarkParser::ParsedBookmarks ParseBookmarksUnsafe(
|
|
}
|
|
parsing_result.bookmarks.push_back(std::move(entry));
|
|
|
|
+#if !BUILDFLAG(IS_ANDROID)
|
|
// Save the favicon. DataURLToFaviconUsage will handle the case where
|
|
// there is no favicon.
|
|
DataURLToFaviconUsage(url, favicon, &parsing_result.favicons);
|
|
+#endif
|
|
|
|
continue;
|
|
}
|
|
diff --git a/cromite_flags/chrome/browser/about_flags_cc/add-bookmark-import-export-actions.inc b/cromite_flags/chrome/browser/about_flags_cc/add-bookmark-import-export-actions.inc
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/cromite_flags/chrome/browser/about_flags_cc/add-bookmark-import-export-actions.inc
|
|
@@ -0,0 +1,12 @@
|
|
+#ifdef FLAG_SECTION
|
|
+
|
|
+#if BUILDFLAG(IS_ANDROID)
|
|
+ {"export-bookmarks-use-saf",
|
|
+ "Use saf for bookmarks export",
|
|
+ "When enabled user can choose where save the exported bookmarks "
|
|
+ "file.", kOsAndroid,
|
|
+ FEATURE_VALUE_TYPE(
|
|
+ chrome::android::kBookmarksExportUseSaf)},
|
|
+#endif
|
|
+
|
|
+#endif
|
|
diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/add-bookmark-import-export-actions.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/add-bookmark-import-export-actions.inc
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/add-bookmark-import-export-actions.inc
|
|
@@ -0,0 +1,4 @@
|
|
+// disabled by default because of an issue on Android 6.0
|
|
+CROMITE_FEATURE(kBookmarksExportUseSaf,
|
|
+ "BookmarksExportUseSaf",
|
|
+ base::FEATURE_DISABLED_BY_DEFAULT);
|
|
diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/add-bookmark-import-export-actions.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/add-bookmark-import-export-actions.inc
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/add-bookmark-import-export-actions.inc
|
|
@@ -0,0 +1 @@
|
|
+BASE_DECLARE_FEATURE(kBookmarksExportUseSaf);
|
|
diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
|
|
--- a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
|
|
+++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
|
|
@@ -52,6 +52,7 @@ import org.chromium.build.annotations.Initializer;
|
|
import org.chromium.build.annotations.NullMarked;
|
|
import org.chromium.build.annotations.Nullable;
|
|
import org.chromium.ui.R;
|
|
+import org.chromium.ui.widget.Toast;
|
|
import org.chromium.ui.UiUtils;
|
|
|
|
import java.io.File;
|
|
@@ -74,6 +75,7 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick
|
|
private static final String TAG = "SelectFileDialog";
|
|
private static final String IMAGE_TYPE = "image";
|
|
private static final String VIDEO_TYPE = "video";
|
|
+ private static final String HTML_TYPE = "html";
|
|
private static final String AUDIO_TYPE = "audio";
|
|
private static final String ALL_TYPES = "*/*";
|
|
private static final String GENERIC_TYPE = "application/octet-stream";
|
|
@@ -323,6 +325,17 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick
|
|
mMimeTypes = convertToSupportedMimeTypes(mFileTypes);
|
|
}
|
|
|
|
+ @CalledByNative
|
|
+ private void showToast(String message) {
|
|
+ ThreadUtils.runOnUiThread(
|
|
+ new Runnable() {
|
|
+ @Override
|
|
+ public void run() {
|
|
+ Toast.makeText(ContextUtils.getApplicationContext(), message, Toast.LENGTH_LONG).show();
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
/**
|
|
* Creates and starts an intent based on the passed fileTypes and capture value.
|
|
*
|
|
@@ -407,7 +420,7 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick
|
|
List<String> missingPermissions = new ArrayList<>();
|
|
String storagePermission = Manifest.permission.READ_EXTERNAL_STORAGE;
|
|
boolean shouldUsePhotoPicker = shouldUsePhotoPicker();
|
|
- if (shouldUsePhotoPicker) {
|
|
+ if (shouldUsePhotoPicker || shouldShowHtmlTypes()) {
|
|
// The permission scenario for accessing media has evolved a bit over the years:
|
|
// Early on, READ_EXTERNAL_STORAGE was required to access media, but that permission was
|
|
// later deprecated. In its place (starting with Android T) READ_MEDIA_IMAGES and
|
|
@@ -447,7 +460,7 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick
|
|
|
|
// TODO(finnur): Remove once we figure out the cause of
|
|
// crbug.com/950024.
|
|
- if (shouldUsePhotoPicker) {
|
|
+ if (shouldUsePhotoPicker || shouldShowHtmlTypes()) {
|
|
if (permissions.length != requestPermissions.length) {
|
|
throw new RuntimeException(
|
|
String.format(
|
|
@@ -464,7 +477,7 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick
|
|
}
|
|
}
|
|
|
|
- if (shouldUsePhotoPicker) {
|
|
+ if (shouldUsePhotoPicker || shouldShowHtmlTypes()) {
|
|
if (permissions[i].equals(storagePermission)
|
|
|| permissions[i].equals(
|
|
Manifest.permission.READ_MEDIA_IMAGES)
|
|
@@ -775,6 +788,7 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick
|
|
mimeTypes.add(mimeType);
|
|
}
|
|
}
|
|
+ if (mimeTypes.size() == 0) return null;
|
|
return mimeTypes;
|
|
}
|
|
|
|
@@ -1160,6 +1174,10 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick
|
|
return countAcceptTypesFor(superType) == mMimeTypes.size();
|
|
}
|
|
|
|
+ private boolean shouldShowHtmlTypes() {
|
|
+ return countAcceptTypesFor(HTML_TYPE) > 0;
|
|
+ }
|
|
+
|
|
/**
|
|
* Checks whether the list of accepted types effectively describes only a single type, which
|
|
* might be wildcard. For example:
|
|
diff --git a/ui/android/java/strings/android_ui_strings.grd b/ui/android/java/strings/android_ui_strings.grd
|
|
--- a/ui/android/java/strings/android_ui_strings.grd
|
|
+++ b/ui/android/java/strings/android_ui_strings.grd
|
|
@@ -184,6 +184,9 @@
|
|
<message name="IDS_PERMISSION_DENIED_ERROR" desc="Toast when the browser is unable to open a file for upload due to missing permission. [CHAR_LIMIT=NONE]">
|
|
Unable to select media due to denied permissions
|
|
</message>
|
|
+ <message name="IDS_SAVING_FILE_ERROR" desc="Toast when the browser is unable to save a file. [CHAR-LIMIT=32]">
|
|
+ Failed to save selected file
|
|
+ </message>
|
|
|
|
<!-- ElidedUrlTextView Url -->
|
|
<message name="IDS_ELIDED_URL_TEXT_VIEW_URL_TRUNCATED" desc="Accessibility announcement when a url switches from expanded to truncated display.">
|
|
diff --git a/ui/shell_dialogs/select_file_dialog.h b/ui/shell_dialogs/select_file_dialog.h
|
|
--- a/ui/shell_dialogs/select_file_dialog.h
|
|
+++ b/ui/shell_dialogs/select_file_dialog.h
|
|
@@ -224,6 +224,8 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
|
const GURL* caller = nullptr);
|
|
bool HasMultipleFileTypeChoices();
|
|
|
|
+ virtual void ShowToast(const std::string& message) = 0;
|
|
+
|
|
protected:
|
|
friend class base::RefCountedThreadSafe<SelectFileDialog>;
|
|
|
|
diff --git a/ui/shell_dialogs/select_file_dialog_android.cc b/ui/shell_dialogs/select_file_dialog_android.cc
|
|
--- a/ui/shell_dialogs/select_file_dialog_android.cc
|
|
+++ b/ui/shell_dialogs/select_file_dialog_android.cc
|
|
@@ -171,6 +171,12 @@ void SelectFileDialogImpl::SelectFileImpl(Type type,
|
|
owning_window->GetJavaObject());
|
|
}
|
|
|
|
+void SelectFileDialogImpl::ShowToast(const std::string& message) {
|
|
+ JNIEnv* env = base::android::AttachCurrentThread();
|
|
+
|
|
+ Java_SelectFileDialog_showToast(env, java_object_, base::android::ConvertUTF8ToJavaString(env, message));
|
|
+}
|
|
+
|
|
SelectFileDialogImpl::~SelectFileDialogImpl() {
|
|
JNIEnv* env = base::android::AttachCurrentThread();
|
|
Java_SelectFileDialog_nativeDestroyed(env, java_object_);
|
|
diff --git a/ui/shell_dialogs/select_file_dialog_android.h b/ui/shell_dialogs/select_file_dialog_android.h
|
|
--- a/ui/shell_dialogs/select_file_dialog_android.h
|
|
+++ b/ui/shell_dialogs/select_file_dialog_android.h
|
|
@@ -49,6 +49,8 @@ class SelectFileDialogImpl : public SelectFileDialog {
|
|
gfx::NativeWindow owning_window,
|
|
const GURL* caller) override;
|
|
|
|
+ void ShowToast(const std::string& message) override;
|
|
+
|
|
protected:
|
|
~SelectFileDialogImpl() override;
|
|
|
|
diff --git a/ui/shell_dialogs/select_file_dialog_linux.cc b/ui/shell_dialogs/select_file_dialog_linux.cc
|
|
--- a/ui/shell_dialogs/select_file_dialog_linux.cc
|
|
+++ b/ui/shell_dialogs/select_file_dialog_linux.cc
|
|
@@ -31,6 +31,10 @@ void SelectFileDialogLinux::ListenerDestroyed() {
|
|
listener_ = nullptr;
|
|
}
|
|
|
|
+void SelectFileDialogLinux::ShowToast(const std::string& message) {
|
|
+ // nothing to do, used only on android
|
|
+}
|
|
+
|
|
bool SelectFileDialogLinux::CallDirectoryExistsOnUIThread(
|
|
const base::FilePath& path) {
|
|
base::ScopedAllowBlocking scoped_allow_blocking;
|
|
diff --git a/ui/shell_dialogs/select_file_dialog_linux.h b/ui/shell_dialogs/select_file_dialog_linux.h
|
|
--- a/ui/shell_dialogs/select_file_dialog_linux.h
|
|
+++ b/ui/shell_dialogs/select_file_dialog_linux.h
|
|
@@ -33,6 +33,8 @@ class SHELL_DIALOGS_EXPORT SelectFileDialogLinux : public SelectFileDialog {
|
|
// BaseShellDialog implementation.
|
|
void ListenerDestroyed() override;
|
|
|
|
+ void ShowToast(const std::string& message) override;
|
|
+
|
|
protected:
|
|
explicit SelectFileDialogLinux(Listener* listener,
|
|
std::unique_ptr<ui::SelectFilePolicy> policy);
|
|
diff --git a/ui/shell_dialogs/select_file_dialog_win.cc b/ui/shell_dialogs/select_file_dialog_win.cc
|
|
--- a/ui/shell_dialogs/select_file_dialog_win.cc
|
|
+++ b/ui/shell_dialogs/select_file_dialog_win.cc
|
|
@@ -192,6 +192,7 @@ class SelectFileDialogImpl : public ui::SelectFileDialog,
|
|
int index);
|
|
|
|
bool HasMultipleFileTypeChoicesImpl() override;
|
|
+ void ShowToast(const std::string& message) override;
|
|
|
|
// Returns the filter to be used while displaying the open/save file dialog.
|
|
// This is computed from the extensions for the file types being opened.
|
|
@@ -269,6 +270,10 @@ bool SelectFileDialogImpl::HasMultipleFileTypeChoicesImpl() {
|
|
return has_multiple_file_type_choices_;
|
|
}
|
|
|
|
+void SelectFileDialogImpl::ShowToast(const std::string& message) {
|
|
+ // nothing to do, used only on android
|
|
+}
|
|
+
|
|
bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow owning_window) const {
|
|
if (!owning_window->GetRootWindow())
|
|
return false;
|
|
--
|