1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/android/bookmarks/bookmarks_bridge.h"
6
7#include "base/android/jni_string.h"
8#include "base/containers/stack_container.h"
9#include "base/i18n/string_compare.h"
10#include "base/prefs/pref_service.h"
11#include "chrome/browser/bookmarks/bookmark_model_factory.h"
12#include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
13#include "chrome/browser/bookmarks/enhanced_bookmarks_features.h"
14#include "chrome/browser/profiles/incognito_helpers.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/profiles/profile_android.h"
17#include "chrome/browser/profiles/profile_manager.h"
18#include "chrome/browser/signin/signin_manager_factory.h"
19#include "chrome/browser/undo/bookmark_undo_service.h"
20#include "chrome/browser/undo/bookmark_undo_service_factory.h"
21#include "chrome/browser/undo/undo_manager.h"
22#include "chrome/common/pref_names.h"
23#include "components/bookmarks/browser/bookmark_model.h"
24#include "components/bookmarks/browser/bookmark_utils.h"
25#include "components/bookmarks/browser/scoped_group_bookmark_actions.h"
26#include "components/bookmarks/common/android/bookmark_type.h"
27#include "components/signin/core/browser/signin_manager.h"
28#include "content/public/browser/browser_thread.h"
29#include "jni/BookmarksBridge_jni.h"
30
31using base::android::AttachCurrentThread;
32using base::android::ConvertUTF8ToJavaString;
33using base::android::ConvertUTF16ToJavaString;
34using base::android::ScopedJavaLocalRef;
35using base::android::ScopedJavaGlobalRef;
36using bookmarks::android::JavaBookmarkIdGetId;
37using bookmarks::android::JavaBookmarkIdGetType;
38using bookmarks::BookmarkType;
39using content::BrowserThread;
40
41namespace {
42
43class BookmarkNodeCreationTimeCompareFunctor {
44 public:
45  bool operator()(const BookmarkNode* lhs, const BookmarkNode* rhs) {
46    return lhs->date_added().ToJavaTime() > rhs->date_added().ToJavaTime();
47  }
48};
49
50class BookmarkTitleComparer {
51 public:
52  explicit BookmarkTitleComparer(const icu::Collator* collator)
53      : collator_(collator) {}
54
55  bool operator()(const BookmarkNode* lhs, const BookmarkNode* rhs) {
56    if (collator_) {
57      return base::i18n::CompareString16WithCollator(
58          collator_, lhs->GetTitle(), rhs->GetTitle()) == UCOL_LESS;
59    } else {
60      return lhs->GetTitle() < rhs->GetTitle();
61    }
62  }
63
64private:
65  const icu::Collator* collator_;
66};
67
68scoped_ptr<icu::Collator> GetICUCollator() {
69  UErrorCode error = U_ZERO_ERROR;
70  scoped_ptr<icu::Collator> collator_;
71  collator_.reset(icu::Collator::createInstance(error));
72  if (U_FAILURE(error))
73    collator_.reset(NULL);
74
75  return collator_.Pass();
76}
77
78}  // namespace
79
80BookmarksBridge::BookmarksBridge(JNIEnv* env,
81                                 jobject obj,
82                                 jobject j_profile)
83    : weak_java_ref_(env, obj),
84      bookmark_model_(NULL),
85      client_(NULL),
86      partner_bookmarks_shim_(NULL) {
87  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
88  profile_ = ProfileAndroid::FromProfileAndroid(j_profile);
89  bookmark_model_ = BookmarkModelFactory::GetForProfile(profile_);
90  client_ = ChromeBookmarkClientFactory::GetForProfile(profile_);
91
92  // Registers the notifications we are interested.
93  bookmark_model_->AddObserver(this);
94
95  // Create the partner Bookmarks shim as early as possible (but don't attach).
96  partner_bookmarks_shim_ = PartnerBookmarksShim::BuildForBrowserContext(
97      chrome::GetBrowserContextRedirectedInIncognito(profile_));
98  partner_bookmarks_shim_->AddObserver(this);
99
100  NotifyIfDoneLoading();
101
102  // Since a sync or import could have started before this class is
103  // initialized, we need to make sure that our initial state is
104  // up to date.
105  if (bookmark_model_->IsDoingExtensiveChanges())
106    ExtensiveBookmarkChangesBeginning(bookmark_model_);
107}
108
109BookmarksBridge::~BookmarksBridge() {
110  bookmark_model_->RemoveObserver(this);
111  if (partner_bookmarks_shim_)
112    partner_bookmarks_shim_->RemoveObserver(this);
113}
114
115void BookmarksBridge::Destroy(JNIEnv*, jobject) {
116  delete this;
117}
118
119// static
120bool BookmarksBridge::RegisterBookmarksBridge(JNIEnv* env) {
121  return RegisterNativesImpl(env);
122}
123
124static jlong Init(JNIEnv* env, jobject obj, jobject j_profile) {
125  BookmarksBridge* delegate = new BookmarksBridge(env, obj, j_profile);
126  return reinterpret_cast<intptr_t>(delegate);
127}
128
129static jlong GetNativeBookmarkModel(JNIEnv* env,
130                                    jclass caller,
131                                    jobject j_profile) {
132  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
133  BookmarkModel *bookmark_model_ = BookmarkModelFactory::GetForProfile(profile);
134  return reinterpret_cast<jlong>(bookmark_model_);
135}
136
137static jboolean IsEnhancedBookmarksFeatureEnabled(JNIEnv* env,
138                                                  jclass clazz,
139                                                  jobject j_profile) {
140  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
141  return IsEnhancedBookmarksEnabled(profile->GetPrefs());
142}
143
144static bool IsEditBookmarksEnabled() {
145  return ProfileManager::GetLastUsedProfile()->GetPrefs()->GetBoolean(
146      bookmarks::prefs::kEditBookmarksEnabled);
147}
148
149static jboolean IsEditBookmarksEnabled(JNIEnv* env, jclass clazz) {
150  return IsEditBookmarksEnabled();
151}
152
153void BookmarksBridge::LoadEmptyPartnerBookmarkShimForTesting(JNIEnv* env,
154                                                             jobject obj) {
155  if (partner_bookmarks_shim_->IsLoaded())
156      return;
157  partner_bookmarks_shim_->SetPartnerBookmarksRoot(
158      new BookmarkPermanentNode(0));
159  DCHECK(partner_bookmarks_shim_->IsLoaded());
160}
161
162ScopedJavaLocalRef<jobject> BookmarksBridge::GetBookmarkByID(JNIEnv* env,
163                                                             jobject obj,
164                                                             jlong id,
165                                                             jint type) {
166  DCHECK(IsLoaded());
167  return CreateJavaBookmark(GetNodeByID(id, type));
168}
169
170bool BookmarksBridge::IsDoingExtensiveChanges(JNIEnv* env, jobject obj) {
171  return bookmark_model_->IsDoingExtensiveChanges();
172}
173
174void BookmarksBridge::GetPermanentNodeIDs(JNIEnv* env,
175                                          jobject obj,
176                                          jobject j_result_obj) {
177  // TODO(kkimlabs): Remove this function.
178  DCHECK(IsLoaded());
179
180  base::StackVector<const BookmarkNode*, 8> permanent_nodes;
181
182  // Save all the permanent nodes.
183  const BookmarkNode* root_node = bookmark_model_->root_node();
184  permanent_nodes->push_back(root_node);
185  for (int i = 0; i < root_node->child_count(); ++i) {
186    permanent_nodes->push_back(root_node->GetChild(i));
187  }
188  permanent_nodes->push_back(
189      partner_bookmarks_shim_->GetPartnerBookmarksRoot());
190
191  // Write the permanent nodes to |j_result_obj|.
192  for (base::StackVector<const BookmarkNode*, 8>::ContainerType::const_iterator
193           it = permanent_nodes->begin();
194       it != permanent_nodes->end();
195       ++it) {
196    if (*it != NULL) {
197      Java_BookmarksBridge_addToBookmarkIdList(
198          env, j_result_obj, (*it)->id(), GetBookmarkType(*it));
199    }
200  }
201}
202
203void BookmarksBridge::GetTopLevelFolderParentIDs(JNIEnv* env,
204                                                 jobject obj,
205                                                 jobject j_result_obj) {
206  Java_BookmarksBridge_addToBookmarkIdList(
207      env, j_result_obj, bookmark_model_->root_node()->id(),
208      GetBookmarkType(bookmark_model_->root_node()));
209  Java_BookmarksBridge_addToBookmarkIdList(
210      env, j_result_obj, bookmark_model_->mobile_node()->id(),
211      GetBookmarkType(bookmark_model_->mobile_node()));
212  Java_BookmarksBridge_addToBookmarkIdList(
213      env, j_result_obj, bookmark_model_->other_node()->id(),
214      GetBookmarkType(bookmark_model_->other_node()));
215}
216
217void BookmarksBridge::GetTopLevelFolderIDs(JNIEnv* env,
218                                           jobject obj,
219                                           jboolean get_special,
220                                           jboolean get_normal,
221                                           jobject j_result_obj) {
222  DCHECK(IsLoaded());
223  std::vector<const BookmarkNode*> top_level_folders;
224
225  if (get_special) {
226    if (client_->managed_node() &&
227        client_->managed_node()->child_count() > 0) {
228      top_level_folders.push_back(client_->managed_node());
229    }
230    if (partner_bookmarks_shim_->HasPartnerBookmarks()) {
231      top_level_folders.push_back(
232          partner_bookmarks_shim_->GetPartnerBookmarksRoot());
233    }
234  }
235  std::size_t special_count = top_level_folders.size();
236
237  if (get_normal) {
238    DCHECK_EQ(bookmark_model_->root_node()->child_count(), 4);
239
240    top_level_folders.push_back(bookmark_model_->bookmark_bar_node());
241
242    const BookmarkNode* mobile_node = bookmark_model_->mobile_node();
243    for (int i = 0; i < mobile_node->child_count(); ++i) {
244      const BookmarkNode* node = mobile_node->GetChild(i);
245      if (node->is_folder()) {
246        top_level_folders.push_back(node);
247      }
248    }
249
250    const BookmarkNode* other_node = bookmark_model_->other_node();
251    for (int i = 0; i < other_node->child_count(); ++i) {
252      const BookmarkNode* node = other_node->GetChild(i);
253      if (node->is_folder()) {
254        top_level_folders.push_back(node);
255      }
256    }
257
258    scoped_ptr<icu::Collator> collator = GetICUCollator();
259    std::stable_sort(top_level_folders.begin() + special_count,
260                     top_level_folders.end(),
261                     BookmarkTitleComparer(collator.get()));
262  }
263
264  for (std::vector<const BookmarkNode*>::const_iterator it =
265      top_level_folders.begin(); it != top_level_folders.end(); ++it) {
266    Java_BookmarksBridge_addToBookmarkIdList(env,
267                                             j_result_obj,
268                                             (*it)->id(),
269                                             GetBookmarkType(*it));
270  }
271}
272
273void BookmarksBridge::GetUncategorizedBookmarkIDs(JNIEnv* env,
274                                                  jobject obj,
275                                                  jobject j_result_obj) {
276  const BookmarkNode* mobile_node = bookmark_model_->mobile_node();
277  for (int i = 0; i < mobile_node->child_count(); ++i) {
278    const BookmarkNode* node = mobile_node->GetChild(i);
279    if (!node->is_folder()) {
280      Java_BookmarksBridge_addToBookmarkIdList(env,
281                                               j_result_obj,
282                                               node->id(),
283                                               GetBookmarkType(node));
284    }
285  }
286
287  const BookmarkNode* other_node = bookmark_model_->other_node();
288  for (int i = 0; i < other_node->child_count(); ++i) {
289    const BookmarkNode* node = other_node->GetChild(i);
290    if (!node->is_folder()) {
291      Java_BookmarksBridge_addToBookmarkIdList(env,
292                                               j_result_obj,
293                                               node->id(),
294                                               GetBookmarkType(node));
295    }
296  }
297}
298
299void BookmarksBridge::GetAllFoldersWithDepths(JNIEnv* env,
300                                              jobject obj,
301                                              jobject j_folders_obj,
302                                              jobject j_depths_obj) {
303  DCHECK(IsLoaded());
304
305  const BookmarkNode* desktop = bookmark_model_->bookmark_bar_node();
306  const BookmarkNode* mobile = bookmark_model_->mobile_node();
307  const BookmarkNode* other = bookmark_model_->other_node();
308
309  scoped_ptr<icu::Collator> collator = GetICUCollator();
310
311  // Vector to temporarily contain all child bookmarks at same level for sorting
312  std::vector<const BookmarkNode*> bookmarkList;
313  // Stack for Depth-First Search of bookmark model. It stores nodes and their
314  // heights.
315  std::stack<std::pair<const BookmarkNode*, int> > stk;
316
317  for (int i = 0; i < mobile->child_count(); ++i) {
318    const BookmarkNode* child = mobile->GetChild(i);
319    if (child->is_folder() && client_->CanBeEditedByUser(child))
320      bookmarkList.push_back(child);
321  }
322  for (int i = 0; i < other->child_count(); ++i) {
323    const BookmarkNode* child = other->GetChild(i);
324    if (child->is_folder() && client_->CanBeEditedByUser(child))
325      bookmarkList.push_back(child);
326  }
327  bookmarkList.push_back(desktop);
328  std::stable_sort(bookmarkList.begin(),
329                   bookmarkList.end(),
330                   BookmarkTitleComparer(collator.get()));
331
332  // Push all sorted top folders in stack and give them depth of 0.
333  // Note the order to push folders to stack should be opposite to the order in
334  // output.
335  for (std::vector<const BookmarkNode*>::reverse_iterator it =
336           bookmarkList.rbegin();
337       it != bookmarkList.rend();
338       ++it) {
339    stk.push(std::make_pair(*it, 0));
340  }
341
342  while (!stk.empty()) {
343    const BookmarkNode* node = stk.top().first;
344    int depth = stk.top().second;
345    stk.pop();
346    Java_BookmarksBridge_addToBookmarkIdListWithDepth(env,
347                                                      j_folders_obj,
348                                                      node->id(),
349                                                      GetBookmarkType(node),
350                                                      j_depths_obj,
351                                                      depth);
352    bookmarkList.clear();
353    for (int i = 0; i < node->child_count(); ++i) {
354      const BookmarkNode* child = node->GetChild(i);
355      if (child->is_folder() && client_->CanBeEditedByUser(child))
356        bookmarkList.push_back(node->GetChild(i));
357    }
358    std::stable_sort(bookmarkList.begin(),
359                     bookmarkList.end(),
360                     BookmarkTitleComparer(collator.get()));
361    for (std::vector<const BookmarkNode*>::reverse_iterator it =
362             bookmarkList.rbegin();
363         it != bookmarkList.rend();
364         ++it) {
365      stk.push(std::make_pair(*it, depth + 1));
366    }
367  }
368}
369
370ScopedJavaLocalRef<jobject> BookmarksBridge::GetMobileFolderId(JNIEnv* env,
371                                                               jobject obj) {
372  const BookmarkNode* mobile_node = bookmark_model_->mobile_node();
373  ScopedJavaLocalRef<jobject> folder_id_obj =
374      Java_BookmarksBridge_createBookmarkId(
375          env, mobile_node->id(), GetBookmarkType(mobile_node));
376  return folder_id_obj;
377}
378
379ScopedJavaLocalRef<jobject> BookmarksBridge::GetOtherFolderId(JNIEnv* env,
380                                                              jobject obj) {
381  const BookmarkNode* other_node = bookmark_model_->other_node();
382  ScopedJavaLocalRef<jobject> folder_id_obj =
383      Java_BookmarksBridge_createBookmarkId(
384          env, other_node->id(), GetBookmarkType(other_node));
385  return folder_id_obj;
386}
387
388ScopedJavaLocalRef<jobject> BookmarksBridge::GetDesktopFolderId(JNIEnv* env,
389                                                                jobject obj) {
390  const BookmarkNode* desktop_node = bookmark_model_->bookmark_bar_node();
391  ScopedJavaLocalRef<jobject> folder_id_obj =
392      Java_BookmarksBridge_createBookmarkId(
393          env, desktop_node->id(), GetBookmarkType(desktop_node));
394  return folder_id_obj;
395}
396
397void BookmarksBridge::GetChildIDs(JNIEnv* env,
398                                  jobject obj,
399                                  jlong id,
400                                  jint type,
401                                  jboolean get_folders,
402                                  jboolean get_bookmarks,
403                                  jobject j_result_obj) {
404  DCHECK(IsLoaded());
405
406  const BookmarkNode* parent = GetNodeByID(id, type);
407  if (!parent->is_folder() || !IsReachable(parent))
408    return;
409
410  // Get the folder contents
411  for (int i = 0; i < parent->child_count(); ++i) {
412    const BookmarkNode* child = parent->GetChild(i);
413    if (!IsFolderAvailable(child) || !IsReachable(child))
414      continue;
415
416    if ((child->is_folder() && get_folders) ||
417        (!child->is_folder() && get_bookmarks)) {
418      Java_BookmarksBridge_addToBookmarkIdList(
419          env, j_result_obj, child->id(), GetBookmarkType(child));
420    }
421  }
422
423  // Partner bookmark root node is under mobile node.
424  if (parent == bookmark_model_->mobile_node() && get_folders &&
425      partner_bookmarks_shim_->HasPartnerBookmarks() &&
426      IsReachable(partner_bookmarks_shim_->GetPartnerBookmarksRoot())) {
427    Java_BookmarksBridge_addToBookmarkIdList(
428        env,
429        j_result_obj,
430        partner_bookmarks_shim_->GetPartnerBookmarksRoot()->id(),
431        BookmarkType::PARTNER);
432  }
433}
434
435void BookmarksBridge::GetAllBookmarkIDsOrderedByCreationDate(
436    JNIEnv* env,
437    jobject obj,
438    jobject j_result_obj) {
439  DCHECK(IsLoaded());
440  std::list<const BookmarkNode*> folders;
441  std::vector<const BookmarkNode*> result;
442  folders.push_back(bookmark_model_->root_node());
443  folders.push_back(partner_bookmarks_shim_->GetPartnerBookmarksRoot());
444
445  for (std::list<const BookmarkNode*>::iterator folder_iter = folders.begin();
446      folder_iter != folders.end(); ++folder_iter) {
447    if (*folder_iter == NULL)
448      continue;
449
450    std::list<const BookmarkNode*>::iterator insert_iter = folder_iter;
451    ++insert_iter;
452
453    for (int i = 0; i < (*folder_iter)->child_count(); ++i) {
454      const BookmarkNode* child = (*folder_iter)->GetChild(i);
455      if (!IsFolderAvailable(child) || !IsReachable(child))
456        continue;
457
458      if (child->is_folder()) {
459        insert_iter = folders.insert(insert_iter, child);
460      } else {
461        result.push_back(child);
462      }
463    }
464  }
465
466  std::sort(
467      result.begin(), result.end(), BookmarkNodeCreationTimeCompareFunctor());
468
469  for (std::vector<const BookmarkNode*>::const_iterator iter = result.begin();
470       iter != result.end();
471       ++iter) {
472    const BookmarkNode* bookmark = *iter;
473    Java_BookmarksBridge_addToBookmarkIdList(
474        env, j_result_obj, bookmark->id(), GetBookmarkType(bookmark));
475  }
476}
477
478void BookmarksBridge::SetBookmarkTitle(JNIEnv* env,
479                                       jobject obj,
480                                       jlong id,
481                                       jint type,
482                                       jstring j_title) {
483  DCHECK(IsLoaded());
484  const BookmarkNode* bookmark = GetNodeByID(id, type);
485  const base::string16 title =
486      base::android::ConvertJavaStringToUTF16(env, j_title);
487
488  if (partner_bookmarks_shim_->IsPartnerBookmark(bookmark)) {
489    partner_bookmarks_shim_->RenameBookmark(bookmark, title);
490  } else {
491    bookmark_model_->SetTitle(bookmark, title);
492  }
493}
494
495void BookmarksBridge::SetBookmarkUrl(JNIEnv* env,
496                                     jobject obj,
497                                     jlong id,
498                                     jint type,
499                                     jstring url) {
500  DCHECK(IsLoaded());
501  bookmark_model_->SetURL(
502      GetNodeByID(id, type),
503      GURL(base::android::ConvertJavaStringToUTF16(env, url)));
504}
505
506bool BookmarksBridge::DoesBookmarkExist(JNIEnv* env,
507                                        jobject obj,
508                                        jlong id,
509                                        jint type) {
510  DCHECK(IsLoaded());
511  return GetNodeByID(id, type);
512}
513
514void BookmarksBridge::GetBookmarksForFolder(JNIEnv* env,
515                                            jobject obj,
516                                            jobject j_folder_id_obj,
517                                            jobject j_callback_obj,
518                                            jobject j_result_obj) {
519  DCHECK(IsLoaded());
520  long folder_id = JavaBookmarkIdGetId(env, j_folder_id_obj);
521  int type = JavaBookmarkIdGetType(env, j_folder_id_obj);
522  const BookmarkNode* folder = GetFolderWithFallback(folder_id, type);
523
524  if (!folder->is_folder() || !IsReachable(folder))
525    return;
526
527  // Recreate the java bookmarkId object due to fallback.
528  ScopedJavaLocalRef<jobject> folder_id_obj =
529      Java_BookmarksBridge_createBookmarkId(
530          env, folder->id(), GetBookmarkType(folder));
531  j_folder_id_obj = folder_id_obj.obj();
532
533  // Get the folder contents.
534  for (int i = 0; i < folder->child_count(); ++i) {
535    const BookmarkNode* node = folder->GetChild(i);
536    if (!IsFolderAvailable(node))
537      continue;
538    ExtractBookmarkNodeInformation(node, j_result_obj);
539  }
540
541  if (folder == bookmark_model_->mobile_node() &&
542      partner_bookmarks_shim_->HasPartnerBookmarks()) {
543    ExtractBookmarkNodeInformation(
544        partner_bookmarks_shim_->GetPartnerBookmarksRoot(),
545        j_result_obj);
546  }
547
548  if (j_callback_obj) {
549    Java_BookmarksCallback_onBookmarksAvailable(
550        env, j_callback_obj, j_folder_id_obj, j_result_obj);
551  }
552}
553
554void BookmarksBridge::GetCurrentFolderHierarchy(JNIEnv* env,
555                                                jobject obj,
556                                                jobject j_folder_id_obj,
557                                                jobject j_callback_obj,
558                                                jobject j_result_obj) {
559  DCHECK(IsLoaded());
560  long folder_id = JavaBookmarkIdGetId(env, j_folder_id_obj);
561  int type = JavaBookmarkIdGetType(env, j_folder_id_obj);
562  const BookmarkNode* folder = GetFolderWithFallback(folder_id, type);
563
564  if (!folder->is_folder() || !IsReachable(folder))
565    return;
566
567  // Recreate the java bookmarkId object due to fallback.
568  ScopedJavaLocalRef<jobject> folder_id_obj =
569      Java_BookmarksBridge_createBookmarkId(
570          env, folder->id(), GetBookmarkType(folder));
571  j_folder_id_obj = folder_id_obj.obj();
572
573  // Get the folder hierarchy.
574  const BookmarkNode* node = folder;
575  while (node) {
576    ExtractBookmarkNodeInformation(node, j_result_obj);
577    node = GetParentNode(node);
578  }
579
580  Java_BookmarksCallback_onBookmarksFolderHierarchyAvailable(
581      env, j_callback_obj, j_folder_id_obj, j_result_obj);
582}
583
584ScopedJavaLocalRef<jobject> BookmarksBridge::AddFolder(JNIEnv* env,
585                                                       jobject obj,
586                                                       jobject j_parent_id_obj,
587                                                       jint index,
588                                                       jstring j_title) {
589  DCHECK(IsLoaded());
590  long bookmark_id = JavaBookmarkIdGetId(env, j_parent_id_obj);
591  int type = JavaBookmarkIdGetType(env, j_parent_id_obj);
592  const BookmarkNode* parent = GetNodeByID(bookmark_id, type);
593
594  const BookmarkNode* new_node = bookmark_model_->AddFolder(
595      parent, index, base::android::ConvertJavaStringToUTF16(env, j_title));
596  if (!new_node) {
597    NOTREACHED();
598    return ScopedJavaLocalRef<jobject>();
599  }
600  ScopedJavaLocalRef<jobject> new_java_obj =
601      Java_BookmarksBridge_createBookmarkId(
602          env, new_node->id(), GetBookmarkType(new_node));
603  return new_java_obj;
604}
605
606void BookmarksBridge::DeleteBookmark(JNIEnv* env,
607                                     jobject obj,
608                                     jobject j_bookmark_id_obj) {
609  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
610  DCHECK(IsLoaded());
611
612  long bookmark_id = JavaBookmarkIdGetId(env, j_bookmark_id_obj);
613  int type = JavaBookmarkIdGetType(env, j_bookmark_id_obj);
614  const BookmarkNode* node = GetNodeByID(bookmark_id, type);
615  if (!IsEditable(node)) {
616    NOTREACHED();
617    return;
618  }
619
620  if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
621    partner_bookmarks_shim_->RemoveBookmark(node);
622  } else {
623    const BookmarkNode* parent_node = GetParentNode(node);
624    bookmark_model_->Remove(parent_node, parent_node->GetIndexOf(node));
625  }
626}
627
628void BookmarksBridge::MoveBookmark(JNIEnv* env,
629                                   jobject obj,
630                                   jobject j_bookmark_id_obj,
631                                   jobject j_parent_id_obj,
632                                   jint index) {
633  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
634  DCHECK(IsLoaded());
635
636  long bookmark_id = JavaBookmarkIdGetId(env, j_bookmark_id_obj);
637  int type = JavaBookmarkIdGetType(env, j_bookmark_id_obj);
638  const BookmarkNode* node = GetNodeByID(bookmark_id, type);
639  if (!IsEditable(node)) {
640    NOTREACHED();
641    return;
642  }
643  bookmark_id = JavaBookmarkIdGetId(env, j_parent_id_obj);
644  type = JavaBookmarkIdGetType(env, j_parent_id_obj);
645  const BookmarkNode* new_parent_node = GetNodeByID(bookmark_id, type);
646  bookmark_model_->Move(node, new_parent_node, index);
647}
648
649ScopedJavaLocalRef<jobject> BookmarksBridge::AddBookmark(
650    JNIEnv* env,
651    jobject obj,
652    jobject j_parent_id_obj,
653    jint index,
654    jstring j_title,
655    jstring j_url) {
656  DCHECK(IsLoaded());
657  long bookmark_id = JavaBookmarkIdGetId(env, j_parent_id_obj);
658  int type = JavaBookmarkIdGetType(env, j_parent_id_obj);
659  const BookmarkNode* parent = GetNodeByID(bookmark_id, type);
660
661  const BookmarkNode* new_node = bookmark_model_->AddURL(
662      parent,
663      index,
664      base::android::ConvertJavaStringToUTF16(env, j_title),
665      GURL(base::android::ConvertJavaStringToUTF16(env, j_url)));
666  if (!new_node) {
667    NOTREACHED();
668    return ScopedJavaLocalRef<jobject>();
669  }
670  ScopedJavaLocalRef<jobject> new_java_obj =
671      Java_BookmarksBridge_createBookmarkId(
672          env, new_node->id(), GetBookmarkType(new_node));
673  return new_java_obj;
674}
675
676void BookmarksBridge::Undo(JNIEnv* env, jobject obj) {
677  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
678  DCHECK(IsLoaded());
679  BookmarkUndoService* undo_service =
680      BookmarkUndoServiceFactory::GetForProfile(profile_);
681  UndoManager* undo_manager = undo_service->undo_manager();
682  undo_manager->Undo();
683}
684
685void BookmarksBridge::StartGroupingUndos(JNIEnv* env, jobject obj) {
686  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
687  DCHECK(IsLoaded());
688  DCHECK(!grouped_bookmark_actions_.get()); // shouldn't have started already
689  grouped_bookmark_actions_.reset(
690      new bookmarks::ScopedGroupBookmarkActions(bookmark_model_));
691}
692
693void BookmarksBridge::EndGroupingUndos(JNIEnv* env, jobject obj) {
694  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
695  DCHECK(IsLoaded());
696  DCHECK(grouped_bookmark_actions_.get()); // should only call after start
697  grouped_bookmark_actions_.reset();
698}
699
700ScopedJavaLocalRef<jobject> BookmarksBridge::CreateJavaBookmark(
701    const BookmarkNode* node) {
702  JNIEnv* env = AttachCurrentThread();
703
704  const BookmarkNode* parent = GetParentNode(node);
705  int64 parent_id = parent ? parent->id() : -1;
706
707  std::string url;
708  if (node->is_url())
709    url = node->url().spec();
710
711  return Java_BookmarksBridge_createBookmarkItem(
712      env,
713      node->id(),
714      GetBookmarkType(node),
715      ConvertUTF16ToJavaString(env, GetTitle(node)).obj(),
716      ConvertUTF8ToJavaString(env, url).obj(),
717      node->is_folder(),
718      parent_id,
719      GetBookmarkType(parent),
720      IsEditable(node),
721      IsManaged(node));
722}
723
724void BookmarksBridge::ExtractBookmarkNodeInformation(const BookmarkNode* node,
725                                                     jobject j_result_obj) {
726  JNIEnv* env = AttachCurrentThread();
727  if (!IsReachable(node))
728    return;
729  Java_BookmarksBridge_addToList(
730      env, j_result_obj, CreateJavaBookmark(node).obj());
731}
732
733const BookmarkNode* BookmarksBridge::GetNodeByID(long node_id, int type) {
734  const BookmarkNode* node;
735  if (type == BookmarkType::PARTNER) {
736    node = partner_bookmarks_shim_->GetNodeByID(
737        static_cast<int64>(node_id));
738  } else {
739    node = bookmarks::GetBookmarkNodeByID(bookmark_model_,
740                                          static_cast<int64>(node_id));
741  }
742  return node;
743}
744
745const BookmarkNode* BookmarksBridge::GetFolderWithFallback(long folder_id,
746                                                           int type) {
747  const BookmarkNode* folder = GetNodeByID(folder_id, type);
748  if (!folder || folder->type() == BookmarkNode::URL ||
749      !IsFolderAvailable(folder)) {
750    if (!client_->managed_node()->empty())
751      folder = client_->managed_node();
752    else
753      folder = bookmark_model_->mobile_node();
754  }
755  return folder;
756}
757
758bool BookmarksBridge::IsEditable(const BookmarkNode* node) const {
759  if (!node || (node->type() != BookmarkNode::FOLDER &&
760      node->type() != BookmarkNode::URL)) {
761    return false;
762  }
763  if (!IsEditBookmarksEnabled())
764    return false;
765  if (partner_bookmarks_shim_->IsPartnerBookmark(node))
766    return partner_bookmarks_shim_->IsEditable(node);
767  return client_->CanBeEditedByUser(node);
768}
769
770bool BookmarksBridge::IsManaged(const BookmarkNode* node) const {
771  return client_->IsDescendantOfManagedNode(node);
772}
773
774const BookmarkNode* BookmarksBridge::GetParentNode(const BookmarkNode* node) {
775  DCHECK(IsLoaded());
776  if (node == partner_bookmarks_shim_->GetPartnerBookmarksRoot()) {
777    return bookmark_model_->mobile_node();
778  } else {
779    return node->parent();
780  }
781}
782
783int BookmarksBridge::GetBookmarkType(const BookmarkNode* node) {
784  if (partner_bookmarks_shim_->IsPartnerBookmark(node))
785    return BookmarkType::PARTNER;
786  else
787    return BookmarkType::NORMAL;
788}
789
790base::string16 BookmarksBridge::GetTitle(const BookmarkNode* node) const {
791  if (partner_bookmarks_shim_->IsPartnerBookmark(node))
792    return partner_bookmarks_shim_->GetTitle(node);
793  return node->GetTitle();
794}
795
796bool BookmarksBridge::IsReachable(const BookmarkNode* node) const {
797  if (!partner_bookmarks_shim_->IsPartnerBookmark(node))
798    return true;
799  return partner_bookmarks_shim_->IsReachable(node);
800}
801
802bool BookmarksBridge::IsLoaded() const {
803  return (bookmark_model_->loaded() && partner_bookmarks_shim_->IsLoaded());
804}
805
806bool BookmarksBridge::IsFolderAvailable(
807    const BookmarkNode* folder) const {
808  // The managed bookmarks folder is not shown if there are no bookmarks
809  // configured via policy.
810  if (folder == client_->managed_node() && folder->empty())
811    return false;
812
813  SigninManager* signin = SigninManagerFactory::GetForProfile(
814      profile_->GetOriginalProfile());
815  return (folder->type() != BookmarkNode::BOOKMARK_BAR &&
816      folder->type() != BookmarkNode::OTHER_NODE) ||
817      (signin && signin->IsAuthenticated());
818}
819
820void BookmarksBridge::NotifyIfDoneLoading() {
821  if (!IsLoaded())
822    return;
823  JNIEnv* env = AttachCurrentThread();
824  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
825  if (obj.is_null())
826    return;
827  Java_BookmarksBridge_bookmarkModelLoaded(env, obj.obj());
828}
829
830// ------------- Observer-related methods ------------- //
831
832void BookmarksBridge::BookmarkModelChanged() {
833  if (!IsLoaded())
834    return;
835
836  // Called when there are changes to the bookmark model. It is most
837  // likely changes to the partner bookmarks.
838  JNIEnv* env = AttachCurrentThread();
839  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
840  if (obj.is_null())
841    return;
842  Java_BookmarksBridge_bookmarkModelChanged(env, obj.obj());
843}
844
845void BookmarksBridge::BookmarkModelLoaded(BookmarkModel* model,
846                                          bool ids_reassigned) {
847  NotifyIfDoneLoading();
848}
849
850void BookmarksBridge::BookmarkModelBeingDeleted(BookmarkModel* model) {
851  if (!IsLoaded())
852    return;
853
854  JNIEnv* env = AttachCurrentThread();
855  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
856  if (obj.is_null())
857    return;
858  Java_BookmarksBridge_bookmarkModelDeleted(env, obj.obj());
859}
860
861void BookmarksBridge::BookmarkNodeMoved(BookmarkModel* model,
862                                        const BookmarkNode* old_parent,
863                                        int old_index,
864                                        const BookmarkNode* new_parent,
865                                        int new_index) {
866  if (!IsLoaded())
867    return;
868
869  JNIEnv* env = AttachCurrentThread();
870  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
871  if (obj.is_null())
872    return;
873  Java_BookmarksBridge_bookmarkNodeMoved(
874      env,
875      obj.obj(),
876      CreateJavaBookmark(old_parent).obj(),
877      old_index,
878      CreateJavaBookmark(new_parent).obj(),
879      new_index);
880}
881
882void BookmarksBridge::BookmarkNodeAdded(BookmarkModel* model,
883                                        const BookmarkNode* parent,
884                                        int index) {
885  if (!IsLoaded())
886    return;
887
888  JNIEnv* env = AttachCurrentThread();
889  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
890  if (obj.is_null())
891    return;
892  Java_BookmarksBridge_bookmarkNodeAdded(
893      env,
894      obj.obj(),
895      CreateJavaBookmark(parent).obj(),
896      index);
897}
898
899void BookmarksBridge::BookmarkNodeRemoved(BookmarkModel* model,
900                                          const BookmarkNode* parent,
901                                          int old_index,
902                                          const BookmarkNode* node,
903                                          const std::set<GURL>& removed_urls) {
904  if (!IsLoaded())
905    return;
906
907  JNIEnv* env = AttachCurrentThread();
908  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
909  if (obj.is_null())
910    return;
911  Java_BookmarksBridge_bookmarkNodeRemoved(
912      env,
913      obj.obj(),
914      CreateJavaBookmark(parent).obj(),
915      old_index,
916      CreateJavaBookmark(node).obj());
917}
918
919void BookmarksBridge::BookmarkNodeChanged(BookmarkModel* model,
920                                          const BookmarkNode* node) {
921  if (!IsLoaded())
922    return;
923
924  JNIEnv* env = AttachCurrentThread();
925  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
926  if (obj.is_null())
927    return;
928  Java_BookmarksBridge_bookmarkNodeChanged(
929      env,
930      obj.obj(),
931      CreateJavaBookmark(node).obj());
932}
933
934void BookmarksBridge::BookmarkNodeChildrenReordered(BookmarkModel* model,
935                                                    const BookmarkNode* node) {
936  if (!IsLoaded())
937    return;
938
939  JNIEnv* env = AttachCurrentThread();
940  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
941  if (obj.is_null())
942    return;
943  Java_BookmarksBridge_bookmarkNodeChildrenReordered(
944      env,
945      obj.obj(),
946      CreateJavaBookmark(node).obj());
947}
948
949void BookmarksBridge::ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {
950  if (!IsLoaded())
951    return;
952
953  JNIEnv* env = AttachCurrentThread();
954  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
955  if (obj.is_null())
956    return;
957  Java_BookmarksBridge_extensiveBookmarkChangesBeginning(env, obj.obj());
958}
959
960void BookmarksBridge::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
961  if (!IsLoaded())
962    return;
963
964  JNIEnv* env = AttachCurrentThread();
965  ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
966  if (obj.is_null())
967    return;
968  Java_BookmarksBridge_extensiveBookmarkChangesEnded(env, obj.obj());
969}
970
971void BookmarksBridge::PartnerShimChanged(PartnerBookmarksShim* shim) {
972  if (!IsLoaded())
973    return;
974
975  BookmarkModelChanged();
976}
977
978void BookmarksBridge::PartnerShimLoaded(PartnerBookmarksShim* shim) {
979  NotifyIfDoneLoading();
980}
981
982void BookmarksBridge::ShimBeingDeleted(PartnerBookmarksShim* shim) {
983  partner_bookmarks_shim_ = NULL;
984}
985