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 --- chrome/android/java/AndroidManifest.xml | 1 - .../res/menu/bookmark_action_bar_menu.xml | 14 + .../browser/bookmarks/BookmarkActionBar.java | 12 + .../browser/bookmarks/BookmarkActivity.java | 23 ++ .../browser/bookmarks/BookmarkBridge.java | 63 +++++ .../browser/bookmarks/BookmarkDelegate.java | 10 + .../browser/bookmarks/BookmarkManager.java | 19 ++ .../browser/bookmarks/BookmarkPage.java | 5 +- .../native_page/NativePageFactory.java | 3 +- chrome/browser/BUILD.gn | 6 +- .../android/bookmarks/bookmark_bridge.cc | 242 ++++++++++++++++++ .../android/bookmarks/bookmark_bridge.h | 23 +- chrome/browser/importer/profile_writer.cc | 12 + chrome/browser/importer/profile_writer.h | 6 + .../strings/android_chrome_strings.grd | 6 + chrome/common/BUILD.gn | 3 + chrome/utility/BUILD.gn | 7 +- .../utility/importer/bookmark_html_reader.cc | 27 +- .../utility/importer/bookmark_html_reader.h | 8 + .../chromium/ui/base/SelectFileDialog.java | 18 +- ui/shell_dialogs/select_file_dialog.h | 2 + .../select_file_dialog_android.cc | 6 + ui/shell_dialogs/select_file_dialog_android.h | 2 + 23 files changed, 504 insertions(+), 14 deletions(-) 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 @@ -38,7 +38,6 @@ by a child template that "extends" this file. {% endif %} - diff --git a/chrome/android/java/res/menu/bookmark_action_bar_menu.xml b/chrome/android/java/res/menu/bookmark_action_bar_menu.xml --- a/chrome/android/java/res/menu/bookmark_action_bar_menu.xml +++ b/chrome/android/java/res/menu/bookmark_action_bar_menu.xml @@ -21,6 +21,20 @@ android:visible="false" app:showAsAction="ifRoom" app:iconTint="@color/default_icon_color_tint_list" /> + + } else if (menuItem.getItemId() == R.id.search_menu_id) { mDelegate.openSearchUI(); return true; + } else if (menuItem.getItemId() == R.id.import_menu_id) { + mDelegate.importBookmarks(); + return true; + } else if (menuItem.getItemId() == R.id.export_menu_id) { + mDelegate.exportBookmarks(); + return true; } SelectionDelegate selectionDelegate = mDelegate.getSelectionDelegate(); @@ -134,6 +140,8 @@ public class BookmarkActionBar extends SelectableListToolbar void showLoadingUi() { setTitle(null); setNavigationButton(NAVIGATION_BUTTON_NONE); + getMenu().findItem(R.id.import_menu_id).setVisible(false); + getMenu().findItem(R.id.export_menu_id).setVisible(false); getMenu().findItem(R.id.search_menu_id).setVisible(false); getMenu().findItem(R.id.edit_menu_id).setVisible(false); } @@ -143,6 +151,8 @@ public class BookmarkActionBar extends SelectableListToolbar super.showNormalView(); if (mDelegate == null) { + getMenu().findItem(R.id.import_menu_id).setVisible(false); + getMenu().findItem(R.id.export_menu_id).setVisible(false); getMenu().findItem(R.id.search_menu_id).setVisible(false); getMenu().findItem(R.id.edit_menu_id).setVisible(false); } @@ -173,6 +183,8 @@ public class BookmarkActionBar extends SelectableListToolbar public void onFolderStateSet(BookmarkId folder) { mCurrentFolder = mDelegate.getModel().getBookmarkById(folder); + getMenu().findItem(R.id.import_menu_id).setVisible(true); + getMenu().findItem(R.id.export_menu_id).setVisible(true); getMenu().findItem(R.id.search_menu_id).setVisible(true); getMenu().findItem(R.id.edit_menu_id).setVisible(mCurrentFolder.isEditable()); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkActivity.java @@ -15,6 +15,7 @@ import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.SnackbarActivity; import org.chromium.components.bookmarks.BookmarkId; import org.chromium.components.embedder_support.util.UrlConstants; +import org.chromium.ui.base.ActivityWindowAndroid; /** * The activity that displays the bookmark UI on the phone. It keeps a {@link BookmarkManager} @@ -24,6 +25,7 @@ import org.chromium.components.embedder_support.util.UrlConstants; public class BookmarkActivity extends SnackbarActivity { private BookmarkManager mBookmarkManager; + private ActivityWindowAndroid mWindowAndroid; static final int EDIT_BOOKMARK_REQUEST_CODE = 14; public static final String INTENT_VISIT_BOOKMARK_ID = "BookmarkEditActivity.VisitBookmarkId"; @@ -38,6 +40,18 @@ public class BookmarkActivity extends SnackbarActivity { if (TextUtils.isEmpty(url)) url = UrlConstants.BOOKMARKS_URL; mBookmarkManager.updateForUrl(url); setContentView(mBookmarkManager.getView()); + + final boolean listenToActivityState = true; + mWindowAndroid = new ActivityWindowAndroid(this, listenToActivityState); + mWindowAndroid.restoreInstanceState(savedInstanceState); + mBookmarkManager.setWindow(mWindowAndroid); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + mWindowAndroid.saveInstanceState(outState); } @Override @@ -54,6 +68,7 @@ public class BookmarkActivity extends SnackbarActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); + mWindowAndroid.onActivityResult(requestCode, resultCode, data); if (requestCode == EDIT_BOOKMARK_REQUEST_CODE && resultCode == RESULT_OK) { BookmarkId bookmarkId = BookmarkId.getBookmarkIdFromString(data.getStringExtra( INTENT_VISIT_BOOKMARK_ID)); @@ -61,6 +76,14 @@ public class BookmarkActivity extends SnackbarActivity { } } + @Override + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + if (mWindowAndroid.handlePermissionResult(requestCode, permissions, grantResults)) + return; + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + /** * @return The {@link BookmarkManager} for testing purposes. */ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java --- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java @@ -4,7 +4,13 @@ package org.chromium.chrome.browser.bookmarks; +import android.content.Intent; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.SystemClock; +import android.provider.Browser; +import android.Manifest.permission; import android.text.TextUtils; import android.util.Pair; @@ -26,6 +32,11 @@ import org.chromium.components.url_formatter.SchemeDisplay; import org.chromium.components.url_formatter.UrlFormatter; import org.chromium.content_public.browser.WebContents; +import org.chromium.chrome.browser.document.ChromeLauncherActivity; +import org.chromium.chrome.browser.IntentHandler; +import org.chromium.ui.base.PageTransition; +import org.chromium.ui.base.WindowAndroid; + import java.util.ArrayList; import java.util.List; @@ -584,6 +595,38 @@ public class BookmarkBridge { mNativeBookmarkBridge, BookmarkBridge.this, 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) { + assert mIsNativeBookmarkModelLoaded; + // check if we have the correct write permission + if (window.hasPermission(permission.WRITE_EXTERNAL_STORAGE)) { + exportBookmarksImpl(); + } else { + String[] requestPermissions = new String[] {permission.WRITE_EXTERNAL_STORAGE}; + window.requestPermissions(requestPermissions, (permissions, grantResults) -> { + if (grantResults.length >= 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + exportBookmarksImpl(); + } + }); + }; + } + + private void exportBookmarksImpl() { + BookmarkBridgeJni.get().exportBookmarks(mNativeBookmarkBridge, BookmarkBridge.this); + } + /** * Synchronously gets a list of bookmarks that match the specified search query. * @param query Keyword used for searching bookmarks. @@ -1006,6 +1049,24 @@ public class BookmarkBridge { depthList.add(depth); } + @CalledByNative + public void bookmarksExported(String bookmarksPath) { + Context context = ContextUtils.getApplicationContext(); + + Intent intent = new Intent(Intent.ACTION_VIEW, 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); + } + private static List> createPairsList(int[] left, int[] right) { List> pairList = new ArrayList>(); for (int i = 0; i < left.length; i++) { @@ -1073,6 +1134,8 @@ public class BookmarkBridge { int getChildCount(long nativeBookmarkBridge, BookmarkBridge caller, long id, int type); void getChildIDs(long nativeBookmarkBridge, BookmarkBridge caller, long id, int type, List bookmarksList); + void importBookmarks(long nativeBookmarkBridge, BookmarkBridge caller, WindowAndroid window); + void exportBookmarks(long nativeBookmarkBridge, BookmarkBridge caller); BookmarkId getChildAt( long nativeBookmarkBridge, BookmarkBridge caller, long id, int type, int index); int getTotalBookmarkCount( diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java --- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java @@ -67,6 +67,16 @@ interface BookmarkDelegate { */ void openSearchUI(); + /** + * Imports bookmarks from user-selected file. + */ + void importBookmarks(); + + /** + * Exports bookmarks to downloads directory. + */ + void exportBookmarks(); + /** * Dismisses the search UI. */ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManager.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManager.java --- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManager.java @@ -32,6 +32,7 @@ import org.chromium.components.browser_ui.util.ConversionUtils; import org.chromium.components.browser_ui.widget.dragreorder.DragStateDelegate; import org.chromium.components.browser_ui.widget.selectable_list.SelectableListLayout; import org.chromium.components.browser_ui.widget.selectable_list.SelectableListToolbar.SearchDelegate; +import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate; import org.chromium.components.favicon.LargeIconBridge; import org.chromium.url.GURL; @@ -54,6 +55,7 @@ public class BookmarkManager private ComponentName mOpenBookmarkComponentName; private ViewGroup mMainView; private BookmarkModel mBookmarkModel; + private ActivityWindowAndroid mWindowAndroid; private BookmarkUndoController mUndoController; private final ObserverList mUIObservers = new ObserverList<>(); private BasicNativePage mNativePage; @@ -327,6 +329,13 @@ public class BookmarkManager mNativePage = nativePage; } + /** + * Sets the Android window that is used by further intents created by the bookmark activity. + */ + public void setWindow(ActivityWindowAndroid window) { + mWindowAndroid = window; + } + /** * @return Current URL representing the UI state of bookmark manager. If no state has been shown * yet in this session, on phone return last used state stored in preference; on tablet @@ -500,6 +509,16 @@ public class BookmarkManager } } + @Override + public void importBookmarks() { + mBookmarkModel.importBookmarks(mWindowAndroid); + } + + @Override + public void exportBookmarks() { + mBookmarkModel.exportBookmarks(mWindowAndroid); + } + @Override public void openSearchUI() { setState(BookmarkUIState.createSearchState()); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java --- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java @@ -13,6 +13,7 @@ import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; import org.chromium.chrome.browser.ui.native_page.BasicNativePage; import org.chromium.chrome.browser.ui.native_page.NativePageHost; import org.chromium.components.embedder_support.util.UrlConstants; +import org.chromium.chrome.browser.app.ChromeActivity; /** * A native page holding a {@link BookmarkManager} on _tablet_. @@ -29,11 +30,13 @@ public class BookmarkPage extends BasicNativePage { * @param host A NativePageHost to load urls. */ public BookmarkPage( - ComponentName componentName, SnackbarManager snackbarManager, NativePageHost host) { + ComponentName componentName, SnackbarManager snackbarManager, NativePageHost host, + ChromeActivity activity) { super(host); mManager = new BookmarkManager(host.getContext(), componentName, false, snackbarManager); mManager.setBasicNativePage(this); + mManager.setWindow(activity.getWindowAndroid()); mTitle = host.getContext().getResources().getString(R.string.bookmarks); initWithView(mManager.getView()); 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 @@ -95,7 +95,8 @@ public class NativePageFactory { protected NativePage buildBookmarksPage(Tab tab) { return new BookmarkPage(mActivity.getComponentName(), mActivity.getSnackbarManager(), - new TabShim(tab, mActivity)); + new TabShim(tab, mActivity), + mActivity); } protected NativePage buildDownloadsPage(Tab tab) { diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn @@ -190,6 +190,8 @@ static_library("browser") { "bitmap_fetcher/bitmap_fetcher_service.h", "bitmap_fetcher/bitmap_fetcher_service_factory.cc", "bitmap_fetcher/bitmap_fetcher_service_factory.h", + "bookmarks/bookmark_html_writer.cc", + "bookmarks/bookmark_html_writer.h", "bluetooth/bluetooth_chooser_context.cc", "bluetooth/bluetooth_chooser_context.h", "bluetooth/bluetooth_chooser_context_factory.cc", @@ -1847,6 +1849,8 @@ static_library("browser") { "web_data_service_factory.h", "window_placement/window_placement_permission_context.cc", "window_placement/window_placement_permission_context.h", + "importer/profile_writer.cc", + "importer/profile_writer.h", ] configs += [ @@ -3373,8 +3377,6 @@ static_library("browser") { "badging/badge_manager_factory.h", "banners/app_banner_manager_desktop.cc", "banners/app_banner_manager_desktop.h", - "bookmarks/bookmark_html_writer.cc", - "bookmarks/bookmark_html_writer.h", "browsing_data/access_context_audit_database.cc", "browsing_data/access_context_audit_database.h", "browsing_data/access_context_audit_service.cc", diff --git a/chrome/browser/android/bookmarks/bookmark_bridge.cc b/chrome/browser/android/bookmarks/bookmark_bridge.cc --- a/chrome/browser/android/bookmarks/bookmark_bridge.cc +++ b/chrome/browser/android/bookmarks/bookmark_bridge.cc @@ -39,6 +39,7 @@ #include "components/bookmarks/common/android/bookmark_type.h" #include "components/bookmarks/common/bookmark_pref_names.h" #include "components/bookmarks/managed/managed_bookmark_service.h" +#include "components/favicon_base/favicon_usage_data.h" #include "components/dom_distiller/core/url_utils.h" #include "components/prefs/pref_service.h" #include "components/query_parser/query_parser.h" @@ -48,6 +49,24 @@ #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" +#include "base/android/content_uri_utils.h" +#include "base/android/path_utils.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/utility/importer/bookmark_html_reader.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/chrome_select_file_policy.h" +#include "chrome/common/importer/imported_bookmark_entry.h" +#include "chrome/common/importer/importer_data_types.h" +#include "chrome/common/url_constants.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" + using base::android::AttachCurrentThread; using base::android::ConvertUTF8ToJavaString; using base::android::ConvertUTF16ToJavaString; @@ -64,6 +83,56 @@ using bookmarks::BookmarkNode; using bookmarks::BookmarkType; using content::BrowserThread; +namespace internal { + +// Returns true if |url| has a valid scheme that we allow to import. We +// filter out the URL with a unsupported scheme. +bool CanImportURL(const GURL& url) { + // The URL is not valid. + if (!url.is_valid()) + return false; + + // Filter out the URLs with unsupported schemes. + const char* const kInvalidSchemes[] = {"wyciwyg", "place"}; + for (size_t i = 0; i < base::size(kInvalidSchemes); ++i) { + if (url.SchemeIs(kInvalidSchemes[i])) + return false; + } + + // Check if |url| is about:blank. + if (url == url::kAboutBlankURL) + return true; + + // If |url| starts with chrome:// or about:, check if it's one of the URLs + // that we support. + if (url.SchemeIs(content::kChromeUIScheme) || + url.SchemeIs(url::kAboutScheme)) { + if (url.host_piece() == chrome::kChromeUIAboutHost) + return true; + + GURL fixed_url(url_formatter::FixupURL(url.spec(), std::string())); + for (size_t i = 0; i < chrome::kNumberOfChromeHostURLs; ++i) { + if (fixed_url.DomainIs(chrome::kChromeHostURLs[i])) + return true; + } + + for (size_t i = 0; i < chrome::kNumberOfChromeDebugURLs; ++i) { + if (fixed_url == chrome::kChromeDebugURLs[i]) + return true; + } + + // If url has either chrome:// or about: schemes but wasn't found in the + // above lists, it means we don't support it, so we don't allow the user + // to import it. + return false; + } + + // Otherwise, we assume the url has a valid (importable) scheme. + return true; +} + +} // internal + namespace { const int kInvalidId = -1; @@ -150,6 +219,10 @@ BookmarkBridge::~BookmarkBridge() { if (partner_bookmarks_shim_) partner_bookmarks_shim_->RemoveObserver(this); reading_list_manager_->RemoveObserver(this); + // 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*, const JavaParamRef&) { @@ -541,6 +614,175 @@ jint BookmarkBridge::GetTotalBookmarkCount( return count; } +void BookmarkBridge::ImportBookmarks(JNIEnv* env, + const JavaParamRef& obj, + const JavaParamRef& 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(nullptr)); + + //NOTE: extension and description are not used on Android, thus not set + ui::SelectFileDialog::FileTypeInfo file_type_info; + + const std::vector v_accept_types = { base::UTF8ToUTF16("text/html") }; + + // Android needs the original MIME types and an additional capture value. + std::pair, bool> accept_types = + std::make_pair(v_accept_types, /* use_media_capture */ false); + + select_file_dialog_->SelectFile( + ui::SelectFileDialog::SELECT_OPEN_FILE, + base::string16(), + export_path_, + &file_type_info, + 0, + base::FilePath::StringType(), + window, + &accept_types + ); +} + +void BookmarkBridge::ExportBookmarks(JNIEnv* env, + const JavaParamRef& obj) { + DCHECK(IsLoaded()); + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + 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_, NULL); + + Java_BookmarkBridge_bookmarksExported(env, obj, ConvertUTF8ToJavaString(env, export_path_.MaybeAsASCII())); + + //NOTE: nothing will be written if write permission has not been granted before + LOG(INFO) << "Bookmarks exported successfully to " << export_path_; +} + +// Attempts to create a TemplateURL from the provided data. |title| is optional. +// If TemplateURL creation fails, returns null. +std::unique_ptr CreateTemplateURL(const base::string16& url, + const base::string16& keyword, + const base::string16& 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(data); +} + +void BookmarkBridge::FileSelected(const base::FilePath& path, int index, + void* params) { + 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; + if (path.IsContentUri()) { + file = base::OpenContentUriForRead(path); + } else { + file.Initialize(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 ""; + } + + if (fileLength > 10 * 1024 * 1024) { + select_file_dialog_->ShowToast("Bookmark file is bigger than 10MB"); + return ""; + } + + std::vector buffer(fileLength); + if (-1 == file.ReadAtCurrentPos(buffer.data(), fileLength)) { + 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 + std::vector bookmarks; + std::vector search_engines; + favicon_base::FaviconUsageDataList favicons; + + bookmark_html_reader::ImportBookmarksFile( + base::Callback(), + base::BindRepeating(internal::CanImportURL), + contents, + &bookmarks, + &search_engines, + &favicons); + + auto *writer = new ProfileWriter(profile_); + + if (!bookmarks.empty()) { + // adding bookmarks will begin extensive changes to the model + writer->AddBookmarksWithModel(bookmark_model_, bookmarks, base::ASCIIToUTF16("Imported")); + } + if (!search_engines.empty()) { + TemplateURLService::OwnedTemplateURLVector owned_template_urls; + for (const auto& search_engine : search_engines) { + std::unique_ptr owned_template_url = CreateTemplateURL( + search_engine.url, search_engine.keyword, search_engine.display_name); + if (owned_template_url) + owned_template_urls.push_back(std::move(owned_template_url)); + } + writer->AddKeywords(std::move(owned_template_urls), false); + } + + std::stringstream message; + message << "Imported " << bookmarks.size() << " bookmarks and " << + search_engines.size() << " search engines from " << path.MaybeAsASCII(); + auto result = message.str(); + + select_file_dialog_->ShowToast(result); + + LOG(INFO) << result; +} + +void BookmarkBridge::FileSelectionCanceled(void* params) { +} + void BookmarkBridge::SetBookmarkTitle(JNIEnv* env, const JavaParamRef& obj, jlong id, diff --git a/chrome/browser/android/bookmarks/bookmark_bridge.h b/chrome/browser/android/bookmarks/bookmark_bridge.h --- a/chrome/browser/android/bookmarks/bookmark_bridge.h +++ b/chrome/browser/android/bookmarks/bookmark_bridge.h @@ -23,6 +23,8 @@ #include "components/bookmarks/browser/base_bookmark_model_observer.h" #include "components/bookmarks/common/android/bookmark_id.h" #include "components/prefs/pref_change_registrar.h" +#include "components/search_engines/template_url.h" +#include "ui/shell_dialogs/select_file_dialog.h" namespace bookmarks { class BookmarkModel; @@ -38,7 +40,8 @@ class Profile; class BookmarkBridge : public bookmarks::BaseBookmarkModelObserver, public PartnerBookmarksShim::Observer, public ReadingListManager::Observer, - public ProfileObserver { + public ProfileObserver, + public ui::SelectFileDialog::Listener { public: BookmarkBridge(JNIEnv* env, const base::android::JavaRef& obj, @@ -54,6 +57,12 @@ class BookmarkBridge : public bookmarks::BaseBookmarkModelObserver, bool IsDoingExtensiveChanges(JNIEnv* env, const base::android::JavaParamRef& obj); + // SelectFileDialog::Listener implementation. + void FileSelected(const base::FilePath& path, + int index, + void* params) override; + void FileSelectionCanceled(void* params) override; + jboolean IsEditBookmarksEnabled(JNIEnv* env); void LoadEmptyPartnerBookmarkShimForTesting( @@ -141,6 +150,13 @@ class BookmarkBridge : public bookmarks::BaseBookmarkModelObserver, jlong id, jint type); + void ImportBookmarks(JNIEnv* env, + const base::android::JavaParamRef& obj, + const base::android::JavaParamRef& java_window); + + void ExportBookmarks(JNIEnv* env, + const base::android::JavaParamRef& obj); + void SetBookmarkTitle(JNIEnv* env, const base::android::JavaParamRef& obj, jlong id, @@ -311,12 +327,14 @@ class BookmarkBridge : public bookmarks::BaseBookmarkModelObserver, void DestroyJavaObject(); Profile* profile_; + base::FilePath export_path_; JavaObjectWeakGlobalRef weak_java_ref_; bookmarks::BookmarkModel* bookmark_model_; // weak bookmarks::ManagedBookmarkService* managed_bookmark_service_; // weak std::unique_ptr grouped_bookmark_actions_; PrefChangeRegistrar pref_change_registrar_; + scoped_refptr select_file_dialog_; // Information about the Partner bookmarks (must check for IsLoaded()). // This is owned by profile. @@ -328,6 +346,9 @@ class BookmarkBridge : public bookmarks::BaseBookmarkModelObserver, // Observes the profile destruction and creation. ScopedObserver profile_observer_{this}; + const std::string FileSelectedImpl(const base::FilePath& path); + void FileSelectedImplOnUIThread(const base::FilePath& path, + const std::string& contents); DISALLOW_COPY_AND_ASSIGN(BookmarkBridge); }; 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 @@ -105,12 +105,14 @@ void ProfileWriter::AddHistoryPage(const history::URLRows& page, HistoryServiceFactory::GetForProfile(profile_, ServiceAccessType::EXPLICIT_ACCESS) ->AddPagesWithDetails(page, visit_source); +#if !defined(OS_ANDROID) // Measure the size of the history page after Auto Import on first run. if (first_run::IsChromeFirstRun() && visit_source == history::SOURCE_IE_IMPORTED) { UMA_HISTOGRAM_COUNTS_1M("Import.ImportedHistorySize.AutoImportFromIE", page.size()); } +#endif } void ProfileWriter::AddHomepage(const GURL& home_page) { @@ -131,6 +133,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& bookmarks, + const base::string16& 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 @@ -12,6 +12,7 @@ #include "base/strings/string16.h" #include "base/time/time.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" @@ -70,6 +71,11 @@ class ProfileWriter : public base::RefCountedThreadSafe { const std::vector& bookmarks, const base::string16& top_level_folder_name); + virtual void AddBookmarksWithModel( + bookmarks::BookmarkModel* model, + const std::vector& bookmarks, + const base::string16& 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/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 @@ -242,6 +242,12 @@ CHAR-LIMIT guidelines: Sites + + Import + + + Export + Virtual Reality diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn --- a/chrome/common/BUILD.gn +++ b/chrome/common/BUILD.gn @@ -400,6 +400,9 @@ static_library("common") { sources += [ "media/chrome_media_drm_bridge_client.cc", "media/chrome_media_drm_bridge_client.h", + ## Bromite dependencies for bookmark import functionality + "importer/imported_bookmark_entry.cc", + "importer/imported_bookmark_entry.h", ] } else { # Non-Android. diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn --- a/chrome/utility/BUILD.gn +++ b/chrome/utility/BUILD.gn @@ -71,8 +71,6 @@ static_library("utility") { if (!is_android) { sources += [ - "importer/bookmark_html_reader.cc", - "importer/bookmark_html_reader.h", "importer/bookmarks_file_importer.cc", "importer/bookmarks_file_importer.h", "importer/external_process_importer_bridge.cc", @@ -187,6 +185,11 @@ static_library("utility") { } } + sources += [ + "importer/bookmark_html_reader.cc", + "importer/bookmark_html_reader.h", + ] + if (use_nss_certs) { sources += [ "importer/nss_decryptor_system_nss.cc", diff --git a/chrome/utility/importer/bookmark_html_reader.cc b/chrome/utility/importer/bookmark_html_reader.cc --- a/chrome/utility/importer/bookmark_html_reader.cc +++ b/chrome/utility/importer/bookmark_html_reader.cc @@ -17,7 +17,9 @@ #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "chrome/common/importer/imported_bookmark_entry.h" +#if !defined(OS_ANDROID) #include "chrome/utility/importer/favicon_reencode.h" +#endif #include "components/search_engines/search_terms_data.h" #include "components/search_engines/template_url.h" #include "net/base/data_url.h" @@ -56,6 +58,7 @@ bool GetAttribute(const std::string& attribute_list, return true; } +#if !defined(OS_ANDROID) // Given the URL of a page and a favicon data URL, adds an appropriate record // to the given favicon usage vector. void DataURLToFaviconUsage(const GURL& link_url, @@ -86,6 +89,7 @@ void DataURLToFaviconUsage(const GURL& link_url, favicons->push_back(usage); } +#endif } // namespace @@ -106,14 +110,28 @@ static std::string stripDt(const std::string& lineDt) { } void ImportBookmarksFile( - base::RepeatingCallback cancellation_callback, - base::RepeatingCallback valid_url_callback, + const base::RepeatingCallback cancellation_callback, + const base::RepeatingCallback valid_url_callback, const base::FilePath& file_path, std::vector* bookmarks, std::vector* search_engines, favicon_base::FaviconUsageDataList* favicons) { std::string content; - base::ReadFileToString(file_path, &content); + if (!base::ReadFileToString(file_path, &content)) { + LOG(ERROR) << "Could not directly read bookmarks import file"; + return; + } + + ImportBookmarksFile(cancellation_callback, valid_url_callback, content, bookmarks, search_engines, favicons); +} + +void ImportBookmarksFile( + base::RepeatingCallback cancellation_callback, + base::RepeatingCallback valid_url_callback, + const std::string& content, + std::vector* bookmarks, + std::vector* search_engines, + favicon_base::FaviconUsageDataList* favicons) { std::vector lines = base::SplitString( content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); @@ -126,6 +144,7 @@ void ImportBookmarksFile( std::vector path; size_t toolbar_folder_index = 0; std::string charset = "UTF-8"; // If no charset is specified, assume utf-8. + for (size_t i = 0; i < lines.size() && (cancellation_callback.is_null() || !cancellation_callback.Run()); @@ -218,10 +237,12 @@ void ImportBookmarksFile( } bookmarks->push_back(entry); +#if !defined(OS_ANDROID) // Save the favicon. DataURLToFaviconUsage will handle the case where // there is no favicon. if (favicons) DataURLToFaviconUsage(url, favicon, favicons); +#endif continue; } diff --git a/chrome/utility/importer/bookmark_html_reader.h b/chrome/utility/importer/bookmark_html_reader.h --- a/chrome/utility/importer/bookmark_html_reader.h +++ b/chrome/utility/importer/bookmark_html_reader.h @@ -51,6 +51,14 @@ void ImportBookmarksFile( std::vector* search_engines, favicon_base::FaviconUsageDataList* favicons); +void ImportBookmarksFile( + const base::RepeatingCallback cancellation_callback, + const base::RepeatingCallback valid_url_callback, + const std::string& content, + std::vector* bookmarks, + std::vector* search_engines, + favicon_base::FaviconUsageDataList* favicons); + // Returns true if |url| should be imported as a search engine, i.e. because it // has replacement terms. Chrome treats such bookmarks as search engines rather // than true bookmarks. 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 @@ -35,6 +35,7 @@ import org.chromium.base.task.AsyncTask; import org.chromium.base.task.PostTask; import org.chromium.base.task.TaskTraits; import org.chromium.ui.R; +import org.chromium.ui.widget.Toast; import org.chromium.ui.UiUtils; import java.io.File; @@ -53,6 +54,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 = "*/*"; @@ -143,6 +145,11 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick mFileTypes = fileTypes; } + @CalledByNative + private void showToast(String message) { + Toast.makeText(ContextUtils.getApplicationContext(), message, Toast.LENGTH_LONG).show(); + } + /** * Creates and starts an intent based on the passed fileTypes and capture value. * @param fileTypes MIME types requested (i.e. "image/*") @@ -170,7 +177,7 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick List missingPermissions = new ArrayList<>(); String storagePermission = Manifest.permission.READ_EXTERNAL_STORAGE; boolean shouldUsePhotoPicker = shouldUsePhotoPicker(); - if (shouldUsePhotoPicker) { + if (shouldUsePhotoPicker || shouldShowHtmlTypes()) { if (!window.hasPermission(storagePermission)) missingPermissions.add(storagePermission); } else { if (((mSupportsImageCapture && shouldShowImageTypes()) @@ -198,7 +205,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("Permissions arrays misaligned: %d != %d", @@ -212,7 +219,7 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick } } - if (shouldUsePhotoPicker && permissions[i].equals(storagePermission)) { + if ((shouldUsePhotoPicker || shouldShowHtmlTypes()) && permissions[i].equals(storagePermission)) { onFileNotSelected(); return; } @@ -358,6 +365,7 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick } if (!mimeTypes.contains(mimeType)) mimeTypes.add(mimeType); } + if (mimeTypes.size() == 0) return null; return mimeTypes; } @@ -659,6 +667,10 @@ public class SelectFileDialog implements WindowAndroid.IntentCallback, PhotoPick return countAcceptTypesFor(superType) == mFileTypes.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/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 @@ -204,6 +204,8 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog void* params); bool HasMultipleFileTypeChoices(); + virtual void ShowToast(const std::string& message) = 0; + protected: friend class base::RefCountedThreadSafe; 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 @@ -140,6 +140,12 @@ void SelectFileDialogImpl::SelectFileImpl( 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() { } 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 @@ -55,6 +55,8 @@ class SelectFileDialogImpl : public SelectFileDialog { gfx::NativeWindow owning_window, void* params) override; + void ShowToast(const std::string& message) override; + protected: ~SelectFileDialogImpl() override; -- 2.17.1