| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/android/provider/chrome_browser_provider.h" |
| |
| #include <cmath> |
| #include <list> |
| #include <utility> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_array.h" |
| #include "base/android/jni_string.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/cancelable_task_tracker.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/android/provider/blocking_ui_thread_async_request.h" |
| #include "chrome/browser/android/provider/bookmark_model_observer_task.h" |
| #include "chrome/browser/android/provider/run_on_ui_thread_blocking.h" |
| #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "chrome/browser/bookmarks/chrome_bookmark_client.h" |
| #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/favicon/favicon_service.h" |
| #include "chrome/browser/favicon/favicon_service_factory.h" |
| #include "chrome/browser/history/android/sqlite_cursor.h" |
| #include "chrome/browser/history/history_service.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/history/top_sites_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/browser/bookmark_utils.h" |
| #include "components/history/core/browser/android/android_history_types.h" |
| #include "components/history/core/browser/top_sites.h" |
| #include "components/search_engines/template_url.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "jni/ChromeBrowserProvider_jni.h" |
| #include "sql/statement.h" |
| #include "ui/base/layout.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/favicon_size.h" |
| |
| using base::android::AttachCurrentThread; |
| using base::android::CheckException; |
| using base::android::ClearException; |
| using base::android::ConvertJavaStringToUTF16; |
| using base::android::ConvertJavaStringToUTF8; |
| using base::android::ConvertUTF8ToJavaString; |
| using base::android::ConvertUTF16ToJavaString; |
| using base::android::GetClass; |
| using base::android::MethodID; |
| using base::android::JavaRef; |
| using base::android::ScopedJavaGlobalRef; |
| using base::android::ScopedJavaLocalRef; |
| using bookmarks::BookmarkModel; |
| using content::BrowserThread; |
| |
| // After refactoring the following class hierarchy has been created in order |
| // to avoid repeating code again for the same basic kind of tasks, to enforce |
| // the correct thread usage and to prevent known race conditions and deadlocks. |
| // |
| // - RunOnUIThreadBlocking: auxiliary class to run methods in the UI thread |
| // blocking the current one until finished. Because of the provider threading |
| // expectations this cannot be used from the UI thread. |
| // |
| // - BookmarkModelTask: base class for all tasks that operate in any way with |
| // the bookmark model. This class ensures that the model is loaded and |
| // prevents possible deadlocks. Derived classes should make use of |
| // RunOnUIThreadBlocking to perform any manipulation of the bookmark model in |
| // the UI thread. The Run method of these tasks cannot be invoked directly |
| // from the UI thread, but RunOnUIThread can be safely used from the UI |
| // thread code of other BookmarkModelTasks. |
| // |
| // - AsyncServiceRequest: base class for any asynchronous requests made to a |
| // Chromium service that require to block the current thread until completed. |
| // Derived classes should make use of RunAsyncRequestOnUIThreadBlocking to |
| // post their requests in the UI thread and return the results synchronously. |
| // All derived classes MUST ALWAYS call RequestCompleted when receiving the |
| // request response. These tasks cannot be invoked from the UI thread. |
| // |
| // - FaviconServiceTask: base class for asynchronous requests that make use of |
| // Chromium's favicon service. See AsyncServiceRequest for more details. |
| // |
| // - HistoryProviderTask: base class for asynchronous requests that make use of |
| // AndroidHistoryProviderService. See AsyncServiceRequest for mode details. |
| // |
| // - SearchTermTask: base class for asynchronous requests that involve the |
| // search term API. Works in the same way as HistoryProviderTask. |
| |
| namespace { |
| |
| const char kDefaultUrlScheme[] = "http://"; |
| const int64 kInvalidContentProviderId = 0; |
| const int64 kInvalidBookmarkId = -1; |
| |
| // ------------- Java-related utility methods ------------- // |
| |
| // Convert a BookmarkNode, |node|, to the java representation of a bookmark node |
| // stored in |*jnode|. Parent node information is optional. |
| void ConvertBookmarkNode( |
| const BookmarkNode* node, |
| const JavaRef<jobject>& parent_node, |
| ScopedJavaGlobalRef<jobject>* jnode) { |
| DCHECK(jnode); |
| if (!node) |
| return; |
| |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jstring> url; |
| if (node->is_url()) |
| url.Reset(ConvertUTF8ToJavaString(env, node->url().spec())); |
| ScopedJavaLocalRef<jstring> title( |
| ConvertUTF16ToJavaString(env, node->GetTitle())); |
| |
| jnode->Reset( |
| Java_BookmarkNode_create( |
| env, node->id(), (jint) node->type(), title.obj(), url.obj(), |
| parent_node.obj())); |
| } |
| |
| jlong ConvertJLongObjectToPrimitive(JNIEnv* env, jobject long_obj) { |
| ScopedJavaLocalRef<jclass> jlong_clazz = GetClass(env, "java/lang/Long"); |
| jmethodID long_value = MethodID::Get<MethodID::TYPE_INSTANCE>( |
| env, jlong_clazz.obj(), "longValue", "()J"); |
| return env->CallLongMethod(long_obj, long_value, NULL); |
| } |
| |
| jboolean ConvertJBooleanObjectToPrimitive(JNIEnv* env, jobject boolean_object) { |
| ScopedJavaLocalRef<jclass> jboolean_clazz = |
| GetClass(env, "java/lang/Boolean"); |
| jmethodID boolean_value = MethodID::Get<MethodID::TYPE_INSTANCE>( |
| env, jboolean_clazz.obj(), "booleanValue", "()Z"); |
| return env->CallBooleanMethod(boolean_object, boolean_value, NULL); |
| } |
| |
| base::Time ConvertJlongToTime(jlong value) { |
| return base::Time::UnixEpoch() + |
| base::TimeDelta::FromMilliseconds((int64)value); |
| } |
| |
| jint ConvertJIntegerToJint(JNIEnv* env, jobject integer_obj) { |
| ScopedJavaLocalRef<jclass> jinteger_clazz = |
| GetClass(env, "java/lang/Integer"); |
| jmethodID int_value = MethodID::Get<MethodID::TYPE_INSTANCE>( |
| env, jinteger_clazz.obj(), "intValue", "()I"); |
| return env->CallIntMethod(integer_obj, int_value, NULL); |
| } |
| |
| std::vector<base::string16> ConvertJStringArrayToString16Array( |
| JNIEnv* env, |
| jobjectArray array) { |
| std::vector<base::string16> results; |
| if (array) { |
| jsize len = env->GetArrayLength(array); |
| for (int i = 0; i < len; i++) { |
| results.push_back(ConvertJavaStringToUTF16(env, |
| static_cast<jstring>(env->GetObjectArrayElement(array, i)))); |
| } |
| } |
| return results; |
| } |
| |
| // ------------- Utility methods used by tasks ------------- // |
| |
| // Parse the given url and return a GURL, appending the default scheme |
| // if one is not present. |
| GURL ParseAndMaybeAppendScheme(const base::string16& url, |
| const char* default_scheme) { |
| GURL gurl(url); |
| if (!gurl.is_valid() && !gurl.has_scheme()) { |
| base::string16 refined_url(base::ASCIIToUTF16(default_scheme)); |
| refined_url.append(url); |
| gurl = GURL(refined_url); |
| } |
| return gurl; |
| } |
| |
| const BookmarkNode* GetChildFolderByTitle(const BookmarkNode* parent, |
| const base::string16& title) { |
| for (int i = 0; i < parent->child_count(); ++i) { |
| if (parent->GetChild(i)->is_folder() && |
| parent->GetChild(i)->GetTitle() == title) { |
| return parent->GetChild(i); |
| } |
| } |
| return NULL; |
| } |
| |
| // ------------- Synchronous task classes ------------- // |
| |
| // Utility task to add a bookmark. |
| class AddBookmarkTask : public BookmarkModelTask { |
| public: |
| explicit AddBookmarkTask(BookmarkModel* model) : BookmarkModelTask(model) {} |
| |
| int64 Run(const base::string16& title, |
| const base::string16& url, |
| const bool is_folder, |
| const int64 parent_id) { |
| int64 result = kInvalidBookmarkId; |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&AddBookmarkTask::RunOnUIThread, |
| model(), title, url, is_folder, parent_id, &result)); |
| return result; |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model, |
| const base::string16& title, |
| const base::string16& url, |
| const bool is_folder, |
| const int64 parent_id, |
| int64* result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(result); |
| GURL gurl = ParseAndMaybeAppendScheme(url, kDefaultUrlScheme); |
| |
| // Check if the bookmark already exists. |
| const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(gurl); |
| if (!node) { |
| const BookmarkNode* parent_node = NULL; |
| if (parent_id >= 0) |
| parent_node = bookmarks::GetBookmarkNodeByID(model, parent_id); |
| if (!parent_node) |
| parent_node = model->bookmark_bar_node(); |
| |
| if (is_folder) |
| node = model->AddFolder(parent_node, parent_node->child_count(), title); |
| else |
| node = model->AddURL(parent_node, 0, title, gurl); |
| } |
| |
| *result = node ? node ->id() : kInvalidBookmarkId; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AddBookmarkTask); |
| }; |
| |
| // Utility method to remove a bookmark. |
| class RemoveBookmarkTask : public BookmarkModelObserverTask { |
| public: |
| explicit RemoveBookmarkTask(BookmarkModel* model) |
| : BookmarkModelObserverTask(model), |
| deleted_(0), |
| id_to_delete_(kInvalidBookmarkId) {} |
| ~RemoveBookmarkTask() override {} |
| |
| int Run(const int64 id) { |
| id_to_delete_ = id; |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&RemoveBookmarkTask::RunOnUIThread, model(), id)); |
| return deleted_; |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model, const int64 id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| const BookmarkNode* node = bookmarks::GetBookmarkNodeByID(model, id); |
| if (node && node->parent()) { |
| const BookmarkNode* parent_node = node->parent(); |
| model->Remove(parent_node, parent_node->GetIndexOf(node)); |
| } |
| } |
| |
| // Verify that the bookmark was actually removed. Called synchronously. |
| void BookmarkNodeRemoved(BookmarkModel* bookmark_model, |
| const BookmarkNode* parent, |
| int old_index, |
| const BookmarkNode* node, |
| const std::set<GURL>& removed_urls) override { |
| if (bookmark_model == model() && node->id() == id_to_delete_) |
| ++deleted_; |
| } |
| |
| private: |
| int deleted_; |
| int64 id_to_delete_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RemoveBookmarkTask); |
| }; |
| |
| // Utility method to remove all bookmarks that the user can edit. |
| class RemoveAllUserBookmarksTask : public BookmarkModelObserverTask { |
| public: |
| explicit RemoveAllUserBookmarksTask(BookmarkModel* model) |
| : BookmarkModelObserverTask(model) {} |
| |
| ~RemoveAllUserBookmarksTask() override {} |
| |
| void Run() { |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&RemoveAllUserBookmarksTask::RunOnUIThread, model())); |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| model->RemoveAllUserBookmarks(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(RemoveAllUserBookmarksTask); |
| }; |
| |
| // Utility method to update a bookmark. |
| class UpdateBookmarkTask : public BookmarkModelObserverTask { |
| public: |
| explicit UpdateBookmarkTask(BookmarkModel* model) |
| : BookmarkModelObserverTask(model), |
| updated_(0), |
| id_to_update_(kInvalidBookmarkId){} |
| ~UpdateBookmarkTask() override {} |
| |
| int Run(const int64 id, |
| const base::string16& title, |
| const base::string16& url, |
| const int64 parent_id) { |
| id_to_update_ = id; |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&UpdateBookmarkTask::RunOnUIThread, |
| model(), id, title, url, parent_id)); |
| return updated_; |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model, |
| const int64 id, |
| const base::string16& title, |
| const base::string16& url, |
| const int64 parent_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| const BookmarkNode* node = bookmarks::GetBookmarkNodeByID(model, id); |
| if (node) { |
| if (node->GetTitle() != title) |
| model->SetTitle(node, title); |
| |
| if (node->type() == BookmarkNode::URL) { |
| GURL bookmark_url = ParseAndMaybeAppendScheme(url, kDefaultUrlScheme); |
| if (bookmark_url != node->url()) |
| model->SetURL(node, bookmark_url); |
| } |
| |
| if (parent_id >= 0 && |
| (!node->parent() || parent_id != node->parent()->id())) { |
| const BookmarkNode* new_parent = |
| bookmarks::GetBookmarkNodeByID(model, parent_id); |
| |
| if (new_parent) |
| model->Move(node, new_parent, 0); |
| } |
| } |
| } |
| |
| // Verify that the bookmark was actually updated. Called synchronously. |
| void BookmarkNodeChanged(BookmarkModel* bookmark_model, |
| const BookmarkNode* node) override { |
| if (bookmark_model == model() && node->id() == id_to_update_) |
| ++updated_; |
| } |
| |
| private: |
| int updated_; |
| int64 id_to_update_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UpdateBookmarkTask); |
| }; |
| |
| // Checks if a node exists in the bookmark model. |
| class BookmarkNodeExistsTask : public BookmarkModelTask { |
| public: |
| explicit BookmarkNodeExistsTask(BookmarkModel* model) |
| : BookmarkModelTask(model) { |
| } |
| |
| bool Run(const int64 id) { |
| bool result = false; |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&BookmarkNodeExistsTask::RunOnUIThread, |
| model(), id, &result)); |
| return result; |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model, |
| const int64 id, |
| bool* result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(result); |
| *result = bookmarks::GetBookmarkNodeByID(model, id) != NULL; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BookmarkNodeExistsTask); |
| }; |
| |
| // Checks if a node belongs to the Mobile Bookmarks hierarchy branch. |
| class IsInMobileBookmarksBranchTask : public BookmarkModelTask { |
| public: |
| explicit IsInMobileBookmarksBranchTask(BookmarkModel* model) |
| : BookmarkModelTask(model) {} |
| |
| bool Run(const int64 id) { |
| bool result = false; |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&IsInMobileBookmarksBranchTask::RunOnUIThread, |
| model(), id, &result)); |
| return result; |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model, |
| const int64 id, |
| bool *result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(result); |
| const BookmarkNode* node = bookmarks::GetBookmarkNodeByID(model, id); |
| const BookmarkNode* mobile_node = model->mobile_node(); |
| while (node && node != mobile_node) |
| node = node->parent(); |
| |
| *result = node == mobile_node; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(IsInMobileBookmarksBranchTask); |
| }; |
| |
| // Creates folder or retrieves its id if already exists. |
| // An invalid parent id is assumed to represent the Mobile Bookmarks folder. |
| // Can only be used to create folders inside the Mobile Bookmarks branch. |
| class CreateBookmarksFolderOnceTask : public BookmarkModelTask { |
| public: |
| explicit CreateBookmarksFolderOnceTask(BookmarkModel* model) |
| : BookmarkModelTask(model) {} |
| |
| int64 Run(const base::string16& title, const int64 parent_id) { |
| int64 result = kInvalidBookmarkId; |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&CreateBookmarksFolderOnceTask::RunOnUIThread, |
| model(), title, parent_id, &result)); |
| return result; |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model, |
| const base::string16& title, |
| const int64 parent_id, |
| int64* result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(result); |
| |
| // Invalid ids are assumed to refer to the Mobile Bookmarks folder. |
| const BookmarkNode* parent = |
| parent_id >= 0 ? bookmarks::GetBookmarkNodeByID(model, parent_id) |
| : model->mobile_node(); |
| DCHECK(parent); |
| |
| bool in_mobile_bookmarks; |
| IsInMobileBookmarksBranchTask::RunOnUIThread(model, parent->id(), |
| &in_mobile_bookmarks); |
| if (!in_mobile_bookmarks) { |
| // The parent folder must be inside the Mobile Bookmarks folder. |
| *result = kInvalidBookmarkId; |
| return; |
| } |
| |
| const BookmarkNode* node = GetChildFolderByTitle(parent, title); |
| if (node) { |
| *result = node->id(); |
| return; |
| } |
| |
| AddBookmarkTask::RunOnUIThread(model, title, base::string16(), true, |
| parent->id(), result); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(CreateBookmarksFolderOnceTask); |
| }; |
| |
| // Creates a Java BookmarkNode object for a node given its id. |
| class GetEditableBookmarkFoldersTask : public BookmarkModelTask { |
| public: |
| GetEditableBookmarkFoldersTask(ChromeBookmarkClient* client, |
| BookmarkModel* model) |
| : BookmarkModelTask(model), client_(client) {} |
| |
| void Run(ScopedJavaGlobalRef<jobject>* jroot) { |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&GetEditableBookmarkFoldersTask::RunOnUIThread, |
| client_, model(), jroot)); |
| } |
| |
| static void RunOnUIThread(ChromeBookmarkClient* client, |
| BookmarkModel* model, |
| ScopedJavaGlobalRef<jobject>* jroot) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| const BookmarkNode* root = model->root_node(); |
| if (!root || !root->is_folder()) |
| return; |
| |
| // The iterative approach is not possible because ScopedGlobalJavaRefs |
| // cannot be copy-constructed, and therefore not used in STL containers. |
| ConvertFolderSubtree(client, AttachCurrentThread(), root, |
| ScopedJavaLocalRef<jobject>(), jroot); |
| } |
| |
| private: |
| static void ConvertFolderSubtree(ChromeBookmarkClient* client, |
| JNIEnv* env, |
| const BookmarkNode* node, |
| const JavaRef<jobject>& parent_folder, |
| ScopedJavaGlobalRef<jobject>* jfolder) { |
| DCHECK(node); |
| DCHECK(node->is_folder()); |
| DCHECK(jfolder); |
| |
| // Global refs should be used here for thread-safety reasons as this task |
| // might be invoked from a thread other than UI. All refs are scoped. |
| ConvertBookmarkNode(node, parent_folder, jfolder); |
| |
| for (int i = 0; i < node->child_count(); ++i) { |
| const BookmarkNode* child = node->GetChild(i); |
| if (child->is_folder() && client->CanBeEditedByUser(child)) { |
| ScopedJavaGlobalRef<jobject> jchild; |
| ConvertFolderSubtree(client, env, child, *jfolder, &jchild); |
| |
| Java_BookmarkNode_addChild(env, jfolder->obj(), jchild.obj()); |
| if (ClearException(env)) { |
| LOG(WARNING) << "Java exception while adding child node."; |
| return; |
| } |
| } |
| } |
| } |
| |
| ChromeBookmarkClient* client_; |
| |
| DISALLOW_COPY_AND_ASSIGN(GetEditableBookmarkFoldersTask); |
| }; |
| |
| // Creates a Java BookmarkNode object for a node given its id. |
| class GetBookmarkNodeTask : public BookmarkModelTask { |
| public: |
| explicit GetBookmarkNodeTask(BookmarkModel* model) |
| : BookmarkModelTask(model) { |
| } |
| |
| void Run(const int64 id, |
| bool get_parent, |
| bool get_children, |
| ScopedJavaGlobalRef<jobject>* jnode) { |
| return RunOnUIThreadBlocking::Run( |
| base::Bind(&GetBookmarkNodeTask::RunOnUIThread, |
| model(), id, get_parent, get_children, jnode)); |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model, |
| const int64 id, |
| bool get_parent, |
| bool get_children, |
| ScopedJavaGlobalRef<jobject>* jnode) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| const BookmarkNode* node = bookmarks::GetBookmarkNodeByID(model, id); |
| if (!node || !jnode) |
| return; |
| |
| ScopedJavaGlobalRef<jobject> jparent; |
| if (get_parent) { |
| ConvertBookmarkNode(node->parent(), ScopedJavaLocalRef<jobject>(), |
| &jparent); |
| } |
| |
| ConvertBookmarkNode(node, jparent, jnode); |
| |
| JNIEnv* env = AttachCurrentThread(); |
| if (!jparent.is_null()) { |
| Java_BookmarkNode_addChild(env, jparent.obj(), jnode->obj()); |
| if (ClearException(env)) { |
| LOG(WARNING) << "Java exception while adding child node."; |
| return; |
| } |
| } |
| |
| if (get_children) { |
| for (int i = 0; i < node->child_count(); ++i) { |
| ScopedJavaGlobalRef<jobject> jchild; |
| ConvertBookmarkNode(node->GetChild(i), *jnode, &jchild); |
| Java_BookmarkNode_addChild(env, jnode->obj(), jchild.obj()); |
| if (ClearException(env)) { |
| LOG(WARNING) << "Java exception while adding child node."; |
| return; |
| } |
| } |
| } |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(GetBookmarkNodeTask); |
| }; |
| |
| // Gets the Mobile Bookmarks node. Using this task ensures the correct |
| // initialization of the bookmark model. |
| class GetMobileBookmarksNodeTask : public BookmarkModelTask { |
| public: |
| explicit GetMobileBookmarksNodeTask(BookmarkModel* model) |
| : BookmarkModelTask(model) {} |
| |
| const BookmarkNode* Run() { |
| const BookmarkNode* result = NULL; |
| RunOnUIThreadBlocking::Run( |
| base::Bind(&GetMobileBookmarksNodeTask::RunOnUIThread, |
| model(), &result)); |
| return result; |
| } |
| |
| static void RunOnUIThread(BookmarkModel* model, const BookmarkNode** result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(result); |
| *result = model->mobile_node(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(GetMobileBookmarksNodeTask); |
| }; |
| |
| // ------------- Aynchronous requests classes ------------- // |
| |
| // Base class for asynchronous blocking requests to Chromium services. |
| // Service: type of the service to use (e.g. HistoryService, FaviconService). |
| template <typename Service> |
| class AsyncServiceRequest : protected BlockingUIThreadAsyncRequest { |
| public: |
| AsyncServiceRequest(Service* service, |
| base::CancelableTaskTracker* cancelable_tracker) |
| : service_(service), cancelable_tracker_(cancelable_tracker) {} |
| |
| Service* service() const { return service_; } |
| |
| base::CancelableTaskTracker* cancelable_tracker() const { |
| return cancelable_tracker_; |
| } |
| |
| private: |
| Service* service_; |
| base::CancelableTaskTracker* cancelable_tracker_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AsyncServiceRequest); |
| }; |
| |
| // Base class for all asynchronous blocking tasks that use the favicon service. |
| class FaviconServiceTask : public AsyncServiceRequest<FaviconService> { |
| public: |
| FaviconServiceTask(base::CancelableTaskTracker* cancelable_tracker, |
| Profile* profile, |
| FaviconService* favicon_service) |
| : AsyncServiceRequest<FaviconService>(favicon_service, |
| cancelable_tracker), |
| profile_(profile) {} |
| |
| Profile* profile() const { return profile_; } |
| |
| private: |
| Profile* profile_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FaviconServiceTask); |
| }; |
| |
| // Retrieves the favicon or touch icon for a URL from the FaviconService. |
| class BookmarkIconFetchTask : public FaviconServiceTask { |
| public: |
| BookmarkIconFetchTask(base::CancelableTaskTracker* cancelable_tracker, |
| Profile* profile, |
| FaviconService* favicon_service) |
| : FaviconServiceTask(cancelable_tracker, profile, favicon_service) {} |
| |
| favicon_base::FaviconRawBitmapResult Run(const GURL& url) { |
| float max_scale = ui::GetScaleForScaleFactor( |
| ResourceBundle::GetSharedInstance().GetMaxScaleFactor()); |
| int desired_size_in_pixel = std::ceil(gfx::kFaviconSize * max_scale); |
| |
| if (service() == NULL) |
| return favicon_base::FaviconRawBitmapResult(); |
| |
| RunAsyncRequestOnUIThreadBlocking( |
| base::Bind(&FaviconService::GetRawFaviconForPageURL, |
| base::Unretained(service()), |
| url, |
| favicon_base::FAVICON | favicon_base::TOUCH_ICON, |
| desired_size_in_pixel, |
| base::Bind(&BookmarkIconFetchTask::OnFaviconRetrieved, |
| base::Unretained(this)), |
| cancelable_tracker())); |
| return result_; |
| } |
| |
| private: |
| void OnFaviconRetrieved( |
| const favicon_base::FaviconRawBitmapResult& bitmap_result) { |
| result_ = bitmap_result; |
| RequestCompleted(); |
| } |
| |
| favicon_base::FaviconRawBitmapResult result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BookmarkIconFetchTask); |
| }; |
| |
| // Base class for all asynchronous blocking tasks that use the Android history |
| // provider service. |
| class HistoryProviderTask |
| : public AsyncServiceRequest<AndroidHistoryProviderService> { |
| public: |
| HistoryProviderTask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker) |
| : AsyncServiceRequest<AndroidHistoryProviderService>(service, |
| cancelable_tracker) { |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(HistoryProviderTask); |
| }; |
| |
| // Adds a bookmark from the API. |
| class AddBookmarkFromAPITask : public HistoryProviderTask { |
| public: |
| AddBookmarkFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker) |
| : HistoryProviderTask(service, cancelable_tracker) {} |
| |
| history::URLID Run(const history::HistoryAndBookmarkRow& row) { |
| RunAsyncRequestOnUIThreadBlocking( |
| base::Bind(&AndroidHistoryProviderService::InsertHistoryAndBookmark, |
| base::Unretained(service()), |
| row, |
| base::Bind(&AddBookmarkFromAPITask::OnBookmarkInserted, |
| base::Unretained(this)), |
| cancelable_tracker())); |
| return result_; |
| } |
| |
| private: |
| void OnBookmarkInserted(history::URLID id) { |
| // Note that here 0 means an invalid id. |
| // This is because it represents a SQLite database row id. |
| result_ = id; |
| RequestCompleted(); |
| } |
| |
| history::URLID result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AddBookmarkFromAPITask); |
| }; |
| |
| // Queries bookmarks from the API. |
| class QueryBookmarksFromAPITask : public HistoryProviderTask { |
| public: |
| QueryBookmarksFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker) |
| : HistoryProviderTask(service, cancelable_tracker), result_(NULL) {} |
| |
| history::AndroidStatement* Run( |
| const std::vector<history::HistoryAndBookmarkRow::ColumnID>& projections, |
| const std::string& selection, |
| const std::vector<base::string16>& selection_args, |
| const std::string& sort_order) { |
| RunAsyncRequestOnUIThreadBlocking( |
| base::Bind(&AndroidHistoryProviderService::QueryHistoryAndBookmarks, |
| base::Unretained(service()), |
| projections, |
| selection, |
| selection_args, |
| sort_order, |
| base::Bind(&QueryBookmarksFromAPITask::OnBookmarksQueried, |
| base::Unretained(this)), |
| cancelable_tracker())); |
| return result_; |
| } |
| |
| private: |
| void OnBookmarksQueried(history::AndroidStatement* statement) { |
| result_ = statement; |
| RequestCompleted(); |
| } |
| |
| history::AndroidStatement* result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(QueryBookmarksFromAPITask); |
| }; |
| |
| // Updates bookmarks from the API. |
| class UpdateBookmarksFromAPITask : public HistoryProviderTask { |
| public: |
| UpdateBookmarksFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker) |
| : HistoryProviderTask(service, cancelable_tracker), result_(0) {} |
| |
| int Run(const history::HistoryAndBookmarkRow& row, |
| const std::string& selection, |
| const std::vector<base::string16>& selection_args) { |
| RunAsyncRequestOnUIThreadBlocking( |
| base::Bind(&AndroidHistoryProviderService::UpdateHistoryAndBookmarks, |
| base::Unretained(service()), |
| row, |
| selection, |
| selection_args, |
| base::Bind(&UpdateBookmarksFromAPITask::OnBookmarksUpdated, |
| base::Unretained(this)), |
| cancelable_tracker())); |
| return result_; |
| } |
| |
| private: |
| void OnBookmarksUpdated(int updated_row_count) { |
| result_ = updated_row_count; |
| RequestCompleted(); |
| } |
| |
| int result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UpdateBookmarksFromAPITask); |
| }; |
| |
| // Removes bookmarks from the API. |
| class RemoveBookmarksFromAPITask : public HistoryProviderTask { |
| public: |
| RemoveBookmarksFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker) |
| : HistoryProviderTask(service, cancelable_tracker), result_(0) {} |
| |
| int Run(const std::string& selection, |
| const std::vector<base::string16>& selection_args) { |
| RunAsyncRequestOnUIThreadBlocking( |
| base::Bind(&AndroidHistoryProviderService::DeleteHistoryAndBookmarks, |
| base::Unretained(service()), |
| selection, |
| selection_args, |
| base::Bind(&RemoveBookmarksFromAPITask::OnBookmarksRemoved, |
| base::Unretained(this)), |
| cancelable_tracker())); |
| return result_; |
| } |
| |
| private: |
| void OnBookmarksRemoved(int removed_row_count) { |
| result_ = removed_row_count; |
| RequestCompleted(); |
| } |
| |
| int result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RemoveBookmarksFromAPITask); |
| }; |
| |
| // Removes history from the API. |
| class RemoveHistoryFromAPITask : public HistoryProviderTask { |
| public: |
| RemoveHistoryFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker) |
| : HistoryProviderTask(service, cancelable_tracker), result_(0) {} |
| |
| int Run(const std::string& selection, |
| const std::vector<base::string16>& selection_args) { |
| RunAsyncRequestOnUIThreadBlocking( |
| base::Bind(&AndroidHistoryProviderService::DeleteHistory, |
| base::Unretained(service()), |
| selection, |
| selection_args, |
| base::Bind(&RemoveHistoryFromAPITask::OnHistoryRemoved, |
| base::Unretained(this)), |
| cancelable_tracker())); |
| return result_; |
| } |
| |
| private: |
| void OnHistoryRemoved(int removed_row_count) { |
| result_ = removed_row_count; |
| RequestCompleted(); |
| } |
| |
| int result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RemoveHistoryFromAPITask); |
| }; |
| |
| // This class provides the common method for the SearchTermAPIHelper. |
| class SearchTermTask : public HistoryProviderTask { |
| protected: |
| SearchTermTask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker, |
| Profile* profile) |
| : HistoryProviderTask(service, cancelable_tracker), profile_(profile) {} |
| |
| // Fill SearchRow's keyword_id and url fields according the given |
| // search_term. Return true if succeeded. |
| void BuildSearchRow(history::SearchRow* row) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| TemplateURLService* template_service = |
| TemplateURLServiceFactory::GetForProfile(profile_); |
| template_service->Load(); |
| |
| const TemplateURL* search_engine = |
| template_service->GetDefaultSearchProvider(); |
| if (search_engine) { |
| const TemplateURLRef* search_url = &search_engine->url_ref(); |
| TemplateURLRef::SearchTermsArgs search_terms_args(row->search_term()); |
| search_terms_args.append_extra_query_params = true; |
| std::string url = search_url->ReplaceSearchTerms( |
| search_terms_args, template_service->search_terms_data()); |
| if (!url.empty()) { |
| row->set_url(GURL(url)); |
| row->set_keyword_id(search_engine->id()); |
| } |
| } |
| } |
| |
| private: |
| Profile* profile_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SearchTermTask); |
| }; |
| |
| // Adds a search term from the API. |
| class AddSearchTermFromAPITask : public SearchTermTask { |
| public: |
| AddSearchTermFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker, |
| Profile* profile) |
| : SearchTermTask(service, cancelable_tracker, profile) {} |
| |
| history::URLID Run(const history::SearchRow& row) { |
| RunAsyncRequestOnUIThreadBlocking( |
| base::Bind(&AddSearchTermFromAPITask::MakeRequestOnUIThread, |
| base::Unretained(this), row)); |
| return result_; |
| } |
| |
| private: |
| void MakeRequestOnUIThread(const history::SearchRow& row) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| history::SearchRow internal_row = row; |
| BuildSearchRow(&internal_row); |
| service()->InsertSearchTerm( |
| internal_row, |
| base::Bind(&AddSearchTermFromAPITask::OnSearchTermInserted, |
| base::Unretained(this)), |
| cancelable_tracker()); |
| } |
| |
| void OnSearchTermInserted(history::URLID id) { |
| // Note that here 0 means an invalid id. |
| // This is because it represents a SQLite database row id. |
| result_ = id; |
| RequestCompleted(); |
| } |
| |
| history::URLID result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AddSearchTermFromAPITask); |
| }; |
| |
| // Queries search terms from the API. |
| class QuerySearchTermsFromAPITask : public SearchTermTask { |
| public: |
| QuerySearchTermsFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker, |
| Profile* profile) |
| : SearchTermTask(service, cancelable_tracker, profile), result_(NULL) {} |
| |
| history::AndroidStatement* Run( |
| const std::vector<history::SearchRow::ColumnID>& projections, |
| const std::string& selection, |
| const std::vector<base::string16>& selection_args, |
| const std::string& sort_order) { |
| RunAsyncRequestOnUIThreadBlocking(base::Bind( |
| &AndroidHistoryProviderService::QuerySearchTerms, |
| base::Unretained(service()), |
| projections, |
| selection, |
| selection_args, |
| sort_order, |
| base::Bind(&QuerySearchTermsFromAPITask::OnSearchTermsQueried, |
| base::Unretained(this)), |
| cancelable_tracker())); |
| return result_; |
| } |
| |
| private: |
| // Callback to return the result. |
| void OnSearchTermsQueried(history::AndroidStatement* statement) { |
| result_ = statement; |
| RequestCompleted(); |
| } |
| |
| history::AndroidStatement* result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(QuerySearchTermsFromAPITask); |
| }; |
| |
| // Updates search terms from the API. |
| class UpdateSearchTermsFromAPITask : public SearchTermTask { |
| public: |
| UpdateSearchTermsFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker, |
| Profile* profile) |
| : SearchTermTask(service, cancelable_tracker, profile), result_(0) {} |
| |
| int Run(const history::SearchRow& row, |
| const std::string& selection, |
| const std::vector<base::string16>& selection_args) { |
| RunAsyncRequestOnUIThreadBlocking( |
| base::Bind(&UpdateSearchTermsFromAPITask::MakeRequestOnUIThread, |
| base::Unretained(this), row, selection, selection_args)); |
| return result_; |
| } |
| |
| private: |
| void MakeRequestOnUIThread( |
| const history::SearchRow& row, |
| const std::string& selection, |
| const std::vector<base::string16>& selection_args) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| history::SearchRow internal_row = row; |
| BuildSearchRow(&internal_row); |
| service()->UpdateSearchTerms( |
| internal_row, |
| selection, |
| selection_args, |
| base::Bind(&UpdateSearchTermsFromAPITask::OnSearchTermsUpdated, |
| base::Unretained(this)), |
| cancelable_tracker()); |
| } |
| |
| void OnSearchTermsUpdated(int updated_row_count) { |
| result_ = updated_row_count; |
| RequestCompleted(); |
| } |
| |
| int result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UpdateSearchTermsFromAPITask); |
| }; |
| |
| // Removes search terms from the API. |
| class RemoveSearchTermsFromAPITask : public SearchTermTask { |
| public: |
| RemoveSearchTermsFromAPITask(AndroidHistoryProviderService* service, |
| base::CancelableTaskTracker* cancelable_tracker, |
| Profile* profile) |
| : SearchTermTask(service, cancelable_tracker, profile), result_() {} |
| |
| int Run(const std::string& selection, |
| const std::vector<base::string16>& selection_args) { |
| RunAsyncRequestOnUIThreadBlocking(base::Bind( |
| &AndroidHistoryProviderService::DeleteSearchTerms, |
| base::Unretained(service()), |
| selection, |
| selection_args, |
| base::Bind(&RemoveSearchTermsFromAPITask::OnSearchTermsDeleted, |
| base::Unretained(this)), |
| cancelable_tracker())); |
| return result_; |
| } |
| |
| private: |
| void OnSearchTermsDeleted(int deleted_row_count) { |
| result_ = deleted_row_count; |
| RequestCompleted(); |
| } |
| |
| int result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RemoveSearchTermsFromAPITask); |
| }; |
| |
| // ------------- Other utility methods (may use tasks) ------------- // |
| |
| // Fills the bookmark |row| with the given java objects. |
| void FillBookmarkRow(JNIEnv* env, |
| jobject obj, |
| jstring url, |
| jobject created, |
| jobject isBookmark, |
| jobject date, |
| jbyteArray favicon, |
| jstring title, |
| jobject visits, |
| jlong parent_id, |
| history::HistoryAndBookmarkRow* row, |
| BookmarkModel* model) { |
| // Needed because of the internal bookmark model task invocation. |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (url) { |
| base::string16 raw_url = ConvertJavaStringToUTF16(env, url); |
| // GURL doesn't accept the URL without protocol, but the Android CTS |
| // allows it. We are trying to prefix with 'http://' to see whether |
| // GURL thinks it is a valid URL. The original url will be stored in |
| // history::BookmarkRow.raw_url_. |
| GURL gurl = ParseAndMaybeAppendScheme(raw_url, kDefaultUrlScheme); |
| row->set_url(gurl); |
| row->set_raw_url(base::UTF16ToUTF8(raw_url)); |
| } |
| |
| if (created) |
| row->set_created(ConvertJlongToTime( |
| ConvertJLongObjectToPrimitive(env, created))); |
| |
| if (isBookmark) |
| row->set_is_bookmark(ConvertJBooleanObjectToPrimitive(env, isBookmark)); |
| |
| if (date) |
| row->set_last_visit_time(ConvertJlongToTime(ConvertJLongObjectToPrimitive( |
| env, date))); |
| |
| if (favicon) { |
| std::vector<uint8> bytes; |
| base::android::JavaByteArrayToByteVector(env, favicon, &bytes); |
| row->set_favicon(base::RefCountedBytes::TakeVector(&bytes)); |
| } |
| |
| if (title) |
| row->set_title(ConvertJavaStringToUTF16(env, title)); |
| |
| if (visits) |
| row->set_visit_count(ConvertJIntegerToJint(env, visits)); |
| |
| // Make sure parent_id is always in the mobile_node branch. |
| IsInMobileBookmarksBranchTask task(model); |
| if (task.Run(parent_id)) |
| row->set_parent_id(parent_id); |
| } |
| |
| // Fills the bookmark |row| with the given java objects if it is not null. |
| void FillSearchRow(JNIEnv* env, |
| jobject obj, |
| jstring search_term, |
| jobject date, |
| history::SearchRow* row) { |
| if (search_term) |
| row->set_search_term(ConvertJavaStringToUTF16(env, search_term)); |
| |
| if (date) |
| row->set_search_time(ConvertJlongToTime(ConvertJLongObjectToPrimitive( |
| env, date))); |
| } |
| |
| } // namespace |
| |
| // ------------- Native initialization and destruction ------------- // |
| |
| static jlong Init(JNIEnv* env, jobject obj) { |
| ChromeBrowserProvider* provider = new ChromeBrowserProvider(env, obj); |
| return reinterpret_cast<intptr_t>(provider); |
| } |
| |
| bool ChromeBrowserProvider::RegisterChromeBrowserProvider(JNIEnv* env) { |
| return RegisterNativesImpl(env); |
| } |
| |
| ChromeBrowserProvider::ChromeBrowserProvider(JNIEnv* env, jobject obj) |
| : weak_java_provider_(env, obj), |
| history_service_observer_(this), |
| handling_extensive_changes_(false) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| profile_ = g_browser_process->profile_manager()->GetLastUsedProfile(); |
| bookmark_model_ = BookmarkModelFactory::GetForProfile(profile_); |
| top_sites_ = TopSitesFactory::GetForProfile(profile_); |
| favicon_service_ = FaviconServiceFactory::GetForProfile( |
| profile_, ServiceAccessType::EXPLICIT_ACCESS), |
| service_.reset(new AndroidHistoryProviderService(profile_)); |
| |
| // Register as observer for service we are interested. |
| bookmark_model_->AddObserver(this); |
| history_service_observer_.Add(HistoryServiceFactory::GetForProfile( |
| profile_, ServiceAccessType::EXPLICIT_ACCESS)); |
| TemplateURLService* template_service = |
| TemplateURLServiceFactory::GetForProfile(profile_); |
| if (!template_service->loaded()) |
| template_service->Load(); |
| } |
| |
| ChromeBrowserProvider::~ChromeBrowserProvider() { |
| bookmark_model_->RemoveObserver(this); |
| } |
| |
| void ChromeBrowserProvider::Destroy(JNIEnv*, jobject) { |
| history_service_observer_.RemoveAll(); |
| delete this; |
| } |
| |
| // ------------- Provider public APIs ------------- // |
| |
| jlong ChromeBrowserProvider::AddBookmark(JNIEnv* env, |
| jobject, |
| jstring jurl, |
| jstring jtitle, |
| jboolean is_folder, |
| jlong parent_id) { |
| base::string16 url; |
| if (jurl) |
| url = ConvertJavaStringToUTF16(env, jurl); |
| base::string16 title = ConvertJavaStringToUTF16(env, jtitle); |
| |
| AddBookmarkTask task(bookmark_model_); |
| return task.Run(title, url, is_folder, parent_id); |
| } |
| |
| jint ChromeBrowserProvider::RemoveBookmark(JNIEnv*, jobject, jlong id) { |
| RemoveBookmarkTask task(bookmark_model_); |
| return task.Run(id); |
| } |
| |
| jint ChromeBrowserProvider::UpdateBookmark(JNIEnv* env, |
| jobject, |
| jlong id, |
| jstring jurl, |
| jstring jtitle, |
| jlong parent_id) { |
| base::string16 url; |
| if (jurl) |
| url = ConvertJavaStringToUTF16(env, jurl); |
| base::string16 title = ConvertJavaStringToUTF16(env, jtitle); |
| |
| UpdateBookmarkTask task(bookmark_model_); |
| return task.Run(id, title, url, parent_id); |
| } |
| |
| // Add the bookmark with the given column values. |
| jlong ChromeBrowserProvider::AddBookmarkFromAPI(JNIEnv* env, |
| jobject obj, |
| jstring url, |
| jobject created, |
| jobject isBookmark, |
| jobject date, |
| jbyteArray favicon, |
| jstring title, |
| jobject visits, |
| jlong parent_id) { |
| DCHECK(url); |
| |
| history::HistoryAndBookmarkRow row; |
| FillBookmarkRow(env, obj, url, created, isBookmark, date, favicon, title, |
| visits, parent_id, &row, bookmark_model_); |
| |
| // URL must be valid. |
| if (row.url().is_empty()) { |
| LOG(ERROR) << "Not a valid URL " << row.raw_url(); |
| return kInvalidContentProviderId; |
| } |
| |
| AddBookmarkFromAPITask task(service_.get(), &cancelable_task_tracker_); |
| return task.Run(row); |
| } |
| |
| ScopedJavaLocalRef<jobject> ChromeBrowserProvider::QueryBookmarkFromAPI( |
| JNIEnv* env, |
| jobject obj, |
| jobjectArray projection, |
| jstring selections, |
| jobjectArray selection_args, |
| jstring sort_order) { |
| // Converts the projection to array of ColumnID and column name. |
| // Used to store the projection column ID according their sequence. |
| std::vector<history::HistoryAndBookmarkRow::ColumnID> query_columns; |
| // Used to store the projection column names according their sequence. |
| std::vector<std::string> columns_name; |
| if (projection) { |
| jsize len = env->GetArrayLength(projection); |
| for (int i = 0; i < len; i++) { |
| std::string name = ConvertJavaStringToUTF8(env, static_cast<jstring>( |
| env->GetObjectArrayElement(projection, i))); |
| history::HistoryAndBookmarkRow::ColumnID id = |
| history::HistoryAndBookmarkRow::GetColumnID(name); |
| if (id == history::HistoryAndBookmarkRow::COLUMN_END) { |
| // Ignore the unknown column; As Android platform will send us |
| // the non public column. |
| continue; |
| } |
| query_columns.push_back(id); |
| columns_name.push_back(name); |
| } |
| } |
| |
| std::vector<base::string16> where_args = |
| ConvertJStringArrayToString16Array(env, selection_args); |
| |
| std::string where_clause; |
| if (selections) { |
| where_clause = ConvertJavaStringToUTF8(env, selections); |
| } |
| |
| std::string sort_clause; |
| if (sort_order) { |
| sort_clause = ConvertJavaStringToUTF8(env, sort_order); |
| } |
| |
| QueryBookmarksFromAPITask task(service_.get(), &cancelable_task_tracker_); |
| history::AndroidStatement* statement = task.Run( |
| query_columns, where_clause, where_args, sort_clause); |
| if (!statement) |
| return ScopedJavaLocalRef<jobject>(); |
| |
| // Creates and returns org.chromium.chrome.browser.database.SQLiteCursor |
| // Java object. |
| return SQLiteCursor::NewJavaSqliteCursor(env, columns_name, statement, |
| service_.get()); |
| } |
| |
| // Updates the bookmarks with the given column values. The value is not given if |
| // it is NULL. |
| jint ChromeBrowserProvider::UpdateBookmarkFromAPI(JNIEnv* env, |
| jobject obj, |
| jstring url, |
| jobject created, |
| jobject isBookmark, |
| jobject date, |
| jbyteArray favicon, |
| jstring title, |
| jobject visits, |
| jlong parent_id, |
| jstring selections, |
| jobjectArray selection_args) { |
| history::HistoryAndBookmarkRow row; |
| FillBookmarkRow(env, obj, url, created, isBookmark, date, favicon, title, |
| visits, parent_id, &row, bookmark_model_); |
| |
| std::vector<base::string16> where_args = |
| ConvertJStringArrayToString16Array(env, selection_args); |
| |
| std::string where_clause; |
| if (selections) |
| where_clause = ConvertJavaStringToUTF8(env, selections); |
| |
| UpdateBookmarksFromAPITask task(service_.get(), &cancelable_task_tracker_); |
| return task.Run(row, where_clause, where_args); |
| } |
| |
| jint ChromeBrowserProvider::RemoveBookmarkFromAPI(JNIEnv* env, |
| jobject obj, |
| jstring selections, |
| jobjectArray selection_args) { |
| std::vector<base::string16> where_args = |
| ConvertJStringArrayToString16Array(env, selection_args); |
| |
| std::string where_clause; |
| if (selections) |
| where_clause = ConvertJavaStringToUTF8(env, selections); |
| |
| RemoveBookmarksFromAPITask task(service_.get(), &cancelable_task_tracker_); |
| return task.Run(where_clause, where_args); |
| } |
| |
| jint ChromeBrowserProvider::RemoveHistoryFromAPI(JNIEnv* env, |
| jobject obj, |
| jstring selections, |
| jobjectArray selection_args) { |
| std::vector<base::string16> where_args = |
| ConvertJStringArrayToString16Array(env, selection_args); |
| |
| std::string where_clause; |
| if (selections) |
| where_clause = ConvertJavaStringToUTF8(env, selections); |
| |
| RemoveHistoryFromAPITask task(service_.get(), &cancelable_task_tracker_); |
| return task.Run(where_clause, where_args); |
| } |
| |
| // Add the search term with the given column values. The value is not given if |
| // it is NULL. |
| jlong ChromeBrowserProvider::AddSearchTermFromAPI(JNIEnv* env, |
| jobject obj, |
| jstring search_term, |
| jobject date) { |
| DCHECK(search_term); |
| |
| history::SearchRow row; |
| FillSearchRow(env, obj, search_term, date, &row); |
| |
| // URL must be valid. |
| if (row.search_term().empty()) { |
| LOG(ERROR) << "Search term is empty."; |
| return kInvalidContentProviderId; |
| } |
| |
| AddSearchTermFromAPITask task(service_.get(), |
| &cancelable_task_tracker_, |
| profile_); |
| return task.Run(row); |
| } |
| |
| ScopedJavaLocalRef<jobject> ChromeBrowserProvider::QuerySearchTermFromAPI( |
| JNIEnv* env, |
| jobject obj, |
| jobjectArray projection, |
| jstring selections, |
| jobjectArray selection_args, |
| jstring sort_order) { |
| // Converts the projection to array of ColumnID and column name. |
| // Used to store the projection column ID according their sequence. |
| std::vector<history::SearchRow::ColumnID> query_columns; |
| // Used to store the projection column names according their sequence. |
| std::vector<std::string> columns_name; |
| if (projection) { |
| jsize len = env->GetArrayLength(projection); |
| for (int i = 0; i < len; i++) { |
| std::string name = ConvertJavaStringToUTF8(env, static_cast<jstring>( |
| env->GetObjectArrayElement(projection, i))); |
| history::SearchRow::ColumnID id = |
| history::SearchRow::GetColumnID(name); |
| if (id == history::SearchRow::COLUMN_END) { |
| LOG(ERROR) << "Can not find " << name; |
| return ScopedJavaLocalRef<jobject>(); |
| } |
| query_columns.push_back(id); |
| columns_name.push_back(name); |
| } |
| } |
| |
| std::vector<base::string16> where_args = |
| ConvertJStringArrayToString16Array(env, selection_args); |
| |
| std::string where_clause; |
| if (selections) { |
| where_clause = ConvertJavaStringToUTF8(env, selections); |
| } |
| |
| std::string sort_clause; |
| if (sort_order) { |
| sort_clause = ConvertJavaStringToUTF8(env, sort_order); |
| } |
| |
| QuerySearchTermsFromAPITask task(service_.get(), |
| &cancelable_task_tracker_, |
| profile_); |
| history::AndroidStatement* statement = task.Run( |
| query_columns, where_clause, where_args, sort_clause); |
| if (!statement) |
| return ScopedJavaLocalRef<jobject>(); |
| // Creates and returns org.chromium.chrome.browser.database.SQLiteCursor |
| // Java object. |
| return SQLiteCursor::NewJavaSqliteCursor(env, columns_name, statement, |
| service_.get()); |
| } |
| |
| // Updates the search terms with the given column values. The value is not |
| // given if it is NULL. |
| jint ChromeBrowserProvider::UpdateSearchTermFromAPI( |
| JNIEnv* env, jobject obj, jstring search_term, jobject date, |
| jstring selections, jobjectArray selection_args) { |
| history::SearchRow row; |
| FillSearchRow(env, obj, search_term, date, &row); |
| |
| std::vector<base::string16> where_args = ConvertJStringArrayToString16Array( |
| env, selection_args); |
| |
| std::string where_clause; |
| if (selections) |
| where_clause = ConvertJavaStringToUTF8(env, selections); |
| |
| UpdateSearchTermsFromAPITask task(service_.get(), |
| &cancelable_task_tracker_, |
| profile_); |
| return task.Run(row, where_clause, where_args); |
| } |
| |
| jint ChromeBrowserProvider::RemoveSearchTermFromAPI( |
| JNIEnv* env, jobject obj, jstring selections, jobjectArray selection_args) { |
| std::vector<base::string16> where_args = |
| ConvertJStringArrayToString16Array(env, selection_args); |
| |
| std::string where_clause; |
| if (selections) |
| where_clause = ConvertJavaStringToUTF8(env, selections); |
| |
| RemoveSearchTermsFromAPITask task(service_.get(), |
| &cancelable_task_tracker_, |
| profile_); |
| return task.Run(where_clause, where_args); |
| } |
| |
| // ------------- Provider custom APIs ------------- // |
| |
| jboolean ChromeBrowserProvider::BookmarkNodeExists( |
| JNIEnv* env, |
| jobject obj, |
| jlong id) { |
| BookmarkNodeExistsTask task(bookmark_model_); |
| return task.Run(id); |
| } |
| |
| jlong ChromeBrowserProvider::CreateBookmarksFolderOnce( |
| JNIEnv* env, |
| jobject obj, |
| jstring jtitle, |
| jlong parent_id) { |
| base::string16 title = ConvertJavaStringToUTF16(env, jtitle); |
| if (title.empty()) |
| return kInvalidBookmarkId; |
| |
| CreateBookmarksFolderOnceTask task(bookmark_model_); |
| return task.Run(title, parent_id); |
| } |
| |
| ScopedJavaLocalRef<jobject> ChromeBrowserProvider::GetEditableBookmarkFolders( |
| JNIEnv* env, |
| jobject obj) { |
| ScopedJavaGlobalRef<jobject> jroot; |
| ChromeBookmarkClient* client = |
| ChromeBookmarkClientFactory::GetForProfile(profile_); |
| BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_); |
| GetEditableBookmarkFoldersTask task(client, model); |
| task.Run(&jroot); |
| return ScopedJavaLocalRef<jobject>(jroot); |
| } |
| |
| void ChromeBrowserProvider::RemoveAllUserBookmarks(JNIEnv* env, jobject obj) { |
| RemoveAllUserBookmarksTask task(bookmark_model_); |
| task.Run(); |
| } |
| |
| ScopedJavaLocalRef<jobject> ChromeBrowserProvider::GetBookmarkNode( |
| JNIEnv* env, jobject obj, jlong id, jboolean get_parent, |
| jboolean get_children) { |
| ScopedJavaGlobalRef<jobject> jnode; |
| GetBookmarkNodeTask task(bookmark_model_); |
| task.Run(id, get_parent, get_children, &jnode); |
| return ScopedJavaLocalRef<jobject>(jnode); |
| } |
| |
| ScopedJavaLocalRef<jobject> ChromeBrowserProvider::GetMobileBookmarksFolder( |
| JNIEnv* env, |
| jobject obj) { |
| ScopedJavaGlobalRef<jobject> jnode; |
| GetMobileBookmarksNodeTask task(bookmark_model_); |
| ConvertBookmarkNode(task.Run(), ScopedJavaLocalRef<jobject>(), &jnode); |
| return ScopedJavaLocalRef<jobject>(jnode); |
| } |
| |
| jboolean ChromeBrowserProvider::IsBookmarkInMobileBookmarksBranch( |
| JNIEnv* env, |
| jobject obj, |
| jlong id) { |
| IsInMobileBookmarksBranchTask task(bookmark_model_); |
| return task.Run(id); |
| } |
| |
| ScopedJavaLocalRef<jbyteArray> ChromeBrowserProvider::GetFaviconOrTouchIcon( |
| JNIEnv* env, jobject obj, jstring jurl) { |
| if (!jurl) |
| return ScopedJavaLocalRef<jbyteArray>(); |
| |
| GURL url = GURL(ConvertJavaStringToUTF16(env, jurl)); |
| BookmarkIconFetchTask favicon_task(&cancelable_task_tracker_, profile_, |
| favicon_service_); |
| favicon_base::FaviconRawBitmapResult bitmap_result = favicon_task.Run(url); |
| |
| if (!bitmap_result.is_valid() || !bitmap_result.bitmap_data.get()) |
| return ScopedJavaLocalRef<jbyteArray>(); |
| |
| return base::android::ToJavaByteArray(env, bitmap_result.bitmap_data->front(), |
| bitmap_result.bitmap_data->size()); |
| } |
| |
| ScopedJavaLocalRef<jbyteArray> ChromeBrowserProvider::GetThumbnail( |
| JNIEnv* env, jobject obj, jstring jurl) { |
| if (!jurl) |
| return ScopedJavaLocalRef<jbyteArray>(); |
| GURL url = GURL(ConvertJavaStringToUTF16(env, jurl)); |
| |
| // GetPageThumbnail is synchronous and can be called from any thread. |
| scoped_refptr<base::RefCountedMemory> thumbnail; |
| if (top_sites_) |
| top_sites_->GetPageThumbnail(url, false, &thumbnail); |
| |
| if (!thumbnail.get() || !thumbnail->front()) { |
| return ScopedJavaLocalRef<jbyteArray>(); |
| } |
| |
| return base::android::ToJavaByteArray(env, thumbnail->front(), |
| thumbnail->size()); |
| } |
| |
| // ------------- Observer-related methods ------------- // |
| |
| void ChromeBrowserProvider::ExtensiveBookmarkChangesBeginning( |
| BookmarkModel* model) { |
| handling_extensive_changes_ = true; |
| } |
| |
| void ChromeBrowserProvider::ExtensiveBookmarkChangesEnded( |
| BookmarkModel* model) { |
| handling_extensive_changes_ = false; |
| BookmarkModelChanged(); |
| } |
| |
| void ChromeBrowserProvider::BookmarkModelChanged() { |
| if (handling_extensive_changes_) |
| return; |
| |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = weak_java_provider_.get(env); |
| if (obj.is_null()) |
| return; |
| |
| Java_ChromeBrowserProvider_onBookmarkChanged(env, obj.obj()); |
| } |
| |
| void ChromeBrowserProvider::OnHistoryChanged() { |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = weak_java_provider_.get(env); |
| if (obj.is_null()) |
| return; |
| Java_ChromeBrowserProvider_onHistoryChanged(env, obj.obj()); |
| } |
| |
| void ChromeBrowserProvider::OnURLVisited(HistoryService* history_service, |
| ui::PageTransition transition, |
| const history::URLRow& row, |
| const history::RedirectList& redirects, |
| base::Time visit_time) { |
| OnHistoryChanged(); |
| } |
| |
| void ChromeBrowserProvider::OnURLsDeleted(HistoryService* history_service, |
| bool all_history, |
| bool expired, |
| const history::URLRows& deleted_rows, |
| const std::set<GURL>& favicon_urls) { |
| OnHistoryChanged(); |
| } |
| |
| void ChromeBrowserProvider::OnKeywordSearchTermUpdated( |
| HistoryService* history_service, |
| const history::URLRow& row, |
| history::KeywordID keyword_id, |
| const base::string16& term) { |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = weak_java_provider_.get(env); |
| if (obj.is_null()) |
| return; |
| Java_ChromeBrowserProvider_onSearchTermChanged(env, obj.obj()); |
| } |
| |
| void ChromeBrowserProvider::OnKeywordSearchTermDeleted( |
| HistoryService* history_service, |
| history::URLID url_id) { |
| } |