Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / webui / ntp / android / bookmarks_handler.cc
1 // Copyright (c) 2012 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/ui/webui/ntp/android/bookmarks_handler.h"
6
7 #include "base/logging.h"
8 #include "base/memory/ref_counted_memory.h"
9 #include "base/metrics/histogram.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "chrome/browser/android/tab_android.h"
14 #include "chrome/browser/bookmarks/bookmark_model.h"
15 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
16 #include "chrome/browser/favicon/favicon_service_factory.h"
17 #include "chrome/browser/profiles/incognito_helpers.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/profiles/profile_manager.h"
20 #include "chrome/browser/ui/webui/favicon_source.h"
21 #include "chrome/common/pref_names.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/url_data_source.h"
24 #include "content/public/browser/web_contents.h"
25 #include "third_party/skia/include/core/SkBitmap.h"
26 #include "ui/gfx/codec/png_codec.h"
27 #include "ui/gfx/color_analysis.h"
28 #include "ui/gfx/favicon_size.h"
29
30 using base::Int64ToString;
31 using content::BrowserThread;
32
33 namespace {
34
35 static const char* kParentIdParam = "parent_id";
36 static const char* kNodeIdParam = "node_id";
37
38 // Defines actions taken by the user over the partner bookmarks on NTP for
39 // NewTabPage.BookmarkActionAndroid histogram.
40 // Should be kept in sync with the values in histograms.xml.
41 enum PartnerBookmarkAction {
42   BOOKMARK_ACTION_DELETE_BOOKMARK_PARTNER = 0,
43   BOOKMARK_ACTION_DELETE_ROOT_FOLDER_PARTNER = 1,
44   BOOKMARK_ACTION_EDIT_BOOKMARK_PARTNER = 2,
45   BOOKMARK_ACTION_EDIT_ROOT_FOLDER_PARTNER = 3,
46   BOOKMARK_ACTION_BUCKET_BOUNDARY = 4
47 };
48
49 // Helper to record a bookmark action in BookmarkActionAndroid histogram.
50 void RecordBookmarkAction(PartnerBookmarkAction type) {
51   UMA_HISTOGRAM_ENUMERATION("NewTabPage.BookmarkActionAndroid", type,
52                             BOOKMARK_ACTION_BUCKET_BOUNDARY);
53 }
54
55 std::string BookmarkTypeAsString(BookmarkNode::Type type) {
56   switch (type) {
57     case BookmarkNode::URL:
58       return "URL";
59     case BookmarkNode::FOLDER:
60       return "FOLDER";
61     case BookmarkNode::BOOKMARK_BAR:
62       return "BOOKMARK_BAR";
63     case BookmarkNode::OTHER_NODE:
64       return "OTHER_NODE";
65     case BookmarkNode::MOBILE:
66       return "MOBILE";
67     default:
68       return "UNKNOWN";
69   }
70 }
71
72 SkColor GetDominantColorForFavicon(scoped_refptr<base::RefCountedMemory> png) {
73   color_utils::GridSampler sampler;
74   // 100 here is the darkness_limit which represents the minimum sum of the RGB
75   // components that is acceptable as a color choice. This can be from 0 to 765.
76   // 665 here is the brightness_limit represents the maximum sum of the RGB
77   // components that is acceptable as a color choice. This can be from 0 to 765.
78   return color_utils::CalculateKMeanColorOfPNG(png, 100, 665, &sampler);
79 }
80
81 }  // namespace
82
83 BookmarksHandler::BookmarksHandler()
84     : bookmark_model_(NULL),
85       partner_bookmarks_shim_(NULL),
86       bookmark_data_requested_(false),
87       extensive_changes_(false) {
88 }
89
90 BookmarksHandler::~BookmarksHandler() {
91   if (bookmark_model_)
92     bookmark_model_->RemoveObserver(this);
93
94   if (partner_bookmarks_shim_)
95     partner_bookmarks_shim_->RemoveObserver(this);
96
97   if (managed_bookmarks_shim_)
98     managed_bookmarks_shim_->RemoveObserver(this);
99 }
100
101 void BookmarksHandler::RegisterMessages() {
102   // Listen for the bookmark change. We need the both bookmark and folder
103   // change, the NotificationService is not sufficient.
104   Profile* profile = Profile::FromBrowserContext(
105       web_ui()->GetWebContents()->GetBrowserContext());
106
107   content::URLDataSource::Add(
108       profile, new FaviconSource(profile, FaviconSource::ANY));
109
110   bookmark_model_ = BookmarkModelFactory::GetForProfile(profile);
111   if (bookmark_model_) {
112     bookmark_model_->AddObserver(this);
113     // Since a sync or import could have started before this class is
114     // initialized, we need to make sure that our initial state is
115     // up to date.
116     extensive_changes_ = bookmark_model_->IsDoingExtensiveChanges();
117   }
118
119   // Create the partner Bookmarks shim as early as possible (but don't attach).
120   if (!partner_bookmarks_shim_) {
121     partner_bookmarks_shim_ = PartnerBookmarksShim::BuildForBrowserContext(
122         chrome::GetBrowserContextRedirectedInIncognito(
123             web_ui()->GetWebContents()->GetBrowserContext()));
124     partner_bookmarks_shim_->AddObserver(this);
125   }
126
127   managed_bookmarks_shim_.reset(new ManagedBookmarksShim(profile->GetPrefs()));
128   managed_bookmarks_shim_->AddObserver(this);
129
130   // Register ourselves as the handler for the bookmark javascript callbacks.
131   web_ui()->RegisterMessageCallback("getBookmarks",
132       base::Bind(&BookmarksHandler::HandleGetBookmarks,
133                  base::Unretained(this)));
134   web_ui()->RegisterMessageCallback("deleteBookmark",
135       base::Bind(&BookmarksHandler::HandleDeleteBookmark,
136                  base::Unretained(this)));
137   web_ui()->RegisterMessageCallback("editBookmark",
138         base::Bind(&BookmarksHandler::HandleEditBookmark,
139                    base::Unretained(this)));
140   web_ui()->RegisterMessageCallback("createHomeScreenBookmarkShortcut",
141       base::Bind(&BookmarksHandler::HandleCreateHomeScreenBookmarkShortcut,
142                  base::Unretained(this)));
143 }
144
145 void BookmarksHandler::HandleGetBookmarks(const base::ListValue* args) {
146   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
147
148   bookmark_data_requested_ = true;
149   if (!AreModelsLoaded())
150     return;  // is handled in Loaded()/PartnerShimLoaded() callback.
151
152   const BookmarkNode* node = GetNodeByID(args);
153   if (node)
154     QueryBookmarkFolder(node);
155   else
156     QueryInitialBookmarks();
157 }
158
159 void BookmarksHandler::HandleDeleteBookmark(const base::ListValue* args) {
160   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
161   if (!AreModelsLoaded())
162     return;
163
164   const BookmarkNode* node = GetNodeByID(args);
165   if (!node)
166     return;
167
168   if (!IsEditable(node)) {
169     NOTREACHED();
170     return;
171   }
172
173   if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
174     if (partner_bookmarks_shim_->GetPartnerBookmarksRoot() == node)
175       RecordBookmarkAction(BOOKMARK_ACTION_DELETE_ROOT_FOLDER_PARTNER);
176     else
177       RecordBookmarkAction(BOOKMARK_ACTION_DELETE_BOOKMARK_PARTNER);
178     partner_bookmarks_shim_->RemoveBookmark(node);
179     return;
180   }
181
182   const BookmarkNode* parent_node = node->parent();
183   bookmark_model_->Remove(parent_node, parent_node->GetIndexOf(node));
184 }
185
186 void BookmarksHandler::HandleEditBookmark(const base::ListValue* args) {
187   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
188   if (!AreModelsLoaded())
189     return;
190
191   const BookmarkNode* node = GetNodeByID(args);
192   if (!node)
193     return;
194
195   if (!IsEditable(node)) {
196     NOTREACHED();
197     return;
198   }
199
200   TabAndroid* tab = TabAndroid::FromWebContents(web_ui()->GetWebContents());
201   if (tab) {
202     if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
203       if (partner_bookmarks_shim_->GetPartnerBookmarksRoot() == node)
204         RecordBookmarkAction(BOOKMARK_ACTION_EDIT_ROOT_FOLDER_PARTNER);
205       else
206         RecordBookmarkAction(BOOKMARK_ACTION_EDIT_BOOKMARK_PARTNER);
207     }
208     tab->EditBookmark(node->id(),
209                       GetTitle(node),
210                       node->is_folder(),
211                       partner_bookmarks_shim_->IsPartnerBookmark(node));
212   }
213 }
214
215 bool BookmarksHandler::AreModelsLoaded() const {
216   Profile* profile = Profile::FromBrowserContext(
217       web_ui()->GetWebContents()->GetBrowserContext());
218   if (!profile)
219     return false;
220
221   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile);
222   if (!model || !model->loaded())
223     return false;
224
225   return partner_bookmarks_shim_ && partner_bookmarks_shim_->IsLoaded();
226 }
227
228 void BookmarksHandler::NotifyModelChanged(const base::DictionaryValue& status) {
229   DCHECK(AreModelsLoaded());
230
231   if (bookmark_data_requested_ && !extensive_changes_)
232     web_ui()->CallJavascriptFunction("ntp.bookmarkChanged", status);
233 }
234
235 std::string BookmarksHandler::GetBookmarkIdForNtp(const BookmarkNode* node) {
236   DCHECK(AreModelsLoaded());
237
238   std::string prefix;
239   if (partner_bookmarks_shim_->IsPartnerBookmark(node))
240     prefix = "p";
241   else if (managed_bookmarks_shim_->IsManagedBookmark(node))
242     prefix = "m";
243   return prefix + Int64ToString(node->id());
244 }
245
246 void BookmarksHandler::SetParentInBookmarksResult(
247     const BookmarkNode* parent,
248     base::DictionaryValue* result) {
249   result->SetString(kParentIdParam, GetBookmarkIdForNtp(parent));
250 }
251
252 void BookmarksHandler::PopulateBookmark(const BookmarkNode* node,
253                                         base::ListValue* result) {
254   if (!result)
255     return;
256
257   DCHECK(AreModelsLoaded());
258   if (!IsReachable(node))
259     return;
260
261   base::DictionaryValue* filler_value = new base::DictionaryValue();
262   filler_value->SetString("title", GetTitle(node));
263   filler_value->SetBoolean("editable", IsEditable(node));
264   if (node->is_url()) {
265     filler_value->SetBoolean("folder", false);
266     filler_value->SetString("url", node->url().spec());
267   } else {
268     filler_value->SetBoolean("folder", true);
269   }
270   filler_value->SetString("id", GetBookmarkIdForNtp(node));
271   filler_value->SetString("type", BookmarkTypeAsString(node->type()));
272   result->Append(filler_value);
273 }
274
275 void BookmarksHandler::PopulateBookmarksInFolder(
276     const BookmarkNode* folder,
277     base::DictionaryValue* result) {
278   DCHECK(AreModelsLoaded());
279   if (!IsReachable(folder))
280     return;
281
282   base::ListValue* bookmarks = new base::ListValue();
283
284   // If this is the Mobile bookmarks folder then add the "Managed bookmarks"
285   // folder first, so that it's the first entry.
286   if (bookmark_model_ && folder == bookmark_model_->mobile_node() &&
287       managed_bookmarks_shim_->HasManagedBookmarks()) {
288     PopulateBookmark(managed_bookmarks_shim_->GetManagedBookmarksRoot(),
289                      bookmarks);
290   }
291
292   for (int i = 0; i < folder->child_count(); i++) {
293     const BookmarkNode* bookmark= folder->GetChild(i);
294     PopulateBookmark(bookmark, bookmarks);
295   }
296
297   // Make sure we iterate over the partner's attach point
298   if (bookmark_model_ && folder == bookmark_model_->mobile_node() &&
299       partner_bookmarks_shim_->HasPartnerBookmarks()) {
300     PopulateBookmark(partner_bookmarks_shim_->GetPartnerBookmarksRoot(),
301                      bookmarks);
302   }
303
304   base::ListValue* folder_hierarchy = new base::ListValue();
305   const BookmarkNode* parent = GetParentOf(folder);
306
307   while (parent != NULL) {
308     base::DictionaryValue* hierarchy_entry = new base::DictionaryValue();
309     if (IsRoot(parent))
310       hierarchy_entry->SetBoolean("root", true);
311
312     hierarchy_entry->SetString("title", GetTitle(parent));
313     hierarchy_entry->SetString("id", GetBookmarkIdForNtp(parent));
314     folder_hierarchy->Append(hierarchy_entry);
315     parent = GetParentOf(parent);
316   }
317
318   result->SetString("title", GetTitle(folder));
319   result->SetString("id", GetBookmarkIdForNtp(folder));
320   result->SetBoolean("root", IsRoot(folder));
321   result->Set("bookmarks", bookmarks);
322   result->Set("hierarchy", folder_hierarchy);
323 }
324
325 void BookmarksHandler::QueryBookmarkFolder(const BookmarkNode* node) {
326   DCHECK(AreModelsLoaded());
327   if (node->is_folder() && IsReachable(node)) {
328     base::DictionaryValue result;
329     PopulateBookmarksInFolder(node, &result);
330     SendResult(result);
331   } else {
332     // If we receive an ID that no longer maps to a bookmark folder, just
333     // return the initial bookmark folder.
334     QueryInitialBookmarks();
335   }
336 }
337
338 void BookmarksHandler::QueryInitialBookmarks() {
339   DCHECK(AreModelsLoaded());
340   base::DictionaryValue result;
341   PopulateBookmarksInFolder(bookmark_model_->mobile_node(), &result);
342   SendResult(result);
343 }
344
345 void BookmarksHandler::SendResult(const base::DictionaryValue& result) {
346   web_ui()->CallJavascriptFunction("ntp.bookmarks", result);
347 }
348
349 void BookmarksHandler::BookmarkModelLoaded(BookmarkModel* model,
350                                            bool ids_reassigned) {
351   if (AreModelsLoaded())
352     BookmarkModelChanged();
353 }
354
355 void BookmarksHandler::PartnerShimChanged(PartnerBookmarksShim* shim) {
356   if (AreModelsLoaded())
357     BookmarkModelChanged();
358 }
359
360 void BookmarksHandler::PartnerShimLoaded(PartnerBookmarksShim* shim) {
361   if (AreModelsLoaded())
362     BookmarkModelChanged();
363 }
364
365 void BookmarksHandler::ShimBeingDeleted(PartnerBookmarksShim* shim) {
366   partner_bookmarks_shim_ = NULL;
367 }
368
369 void BookmarksHandler::OnManagedBookmarksChanged() {
370   if (AreModelsLoaded())
371     BookmarkModelChanged();
372 }
373
374 void BookmarksHandler::ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {
375   extensive_changes_ = true;
376 }
377
378 void BookmarksHandler::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
379   extensive_changes_ = false;
380   if (AreModelsLoaded())
381     BookmarkModelChanged();
382 }
383
384 void BookmarksHandler::BookmarkNodeRemoved(BookmarkModel* model,
385                                            const BookmarkNode* parent,
386                                            int old_index,
387                                            const BookmarkNode* node) {
388   if (!AreModelsLoaded())
389     return;
390
391   base::DictionaryValue result;
392   SetParentInBookmarksResult(parent, &result);
393   result.SetString(kNodeIdParam, Int64ToString(node->id()));
394   NotifyModelChanged(result);
395 }
396
397 void BookmarksHandler::BookmarkAllNodesRemoved(BookmarkModel* model) {
398   if (!AreModelsLoaded())
399     return;
400
401   if (bookmark_data_requested_ && !extensive_changes_)
402     web_ui()->CallJavascriptFunction("ntp.bookmarkChanged");
403 }
404
405 void BookmarksHandler::BookmarkNodeAdded(
406     BookmarkModel* model, const BookmarkNode* parent, int index) {
407   if (!AreModelsLoaded())
408     return;
409
410   base::DictionaryValue result;
411   SetParentInBookmarksResult(parent, &result);
412   NotifyModelChanged(result);
413 }
414
415 void BookmarksHandler::BookmarkNodeChanged(BookmarkModel* model,
416                                            const BookmarkNode* node) {
417   if (!AreModelsLoaded())
418     return;
419
420   DCHECK(!partner_bookmarks_shim_->IsPartnerBookmark(node));
421   base::DictionaryValue result;
422   SetParentInBookmarksResult(node->parent(), &result);
423   result.SetString(kNodeIdParam, Int64ToString(node->id()));
424   NotifyModelChanged(result);
425 }
426
427 void BookmarksHandler::BookmarkModelChanged() {
428   if (!AreModelsLoaded())
429     return;
430
431   if (bookmark_data_requested_ && !extensive_changes_)
432     web_ui()->CallJavascriptFunction("ntp.bookmarkChanged");
433 }
434
435 void BookmarksHandler::HandleCreateHomeScreenBookmarkShortcut(
436     const base::ListValue* args) {
437   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
438   if (!AreModelsLoaded())
439     return;
440
441   Profile* profile = Profile::FromBrowserContext(
442       web_ui()->GetWebContents()->GetBrowserContext());
443   if (!profile)
444     return;
445
446   const BookmarkNode* node = GetNodeByID(args);
447   if (!node)
448     return;
449
450   FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
451       profile, Profile::EXPLICIT_ACCESS);
452   favicon_service->GetRawFaviconForURL(
453       FaviconService::FaviconForURLParams(
454           node->url(),
455           chrome::TOUCH_PRECOMPOSED_ICON | chrome::TOUCH_ICON |
456               chrome::FAVICON,
457           0),  // request the largest icon.
458       ui::SCALE_FACTOR_100P,  // density doesn't matter for the largest icon.
459       base::Bind(&BookmarksHandler::OnShortcutFaviconDataAvailable,
460                  base::Unretained(this),
461                  node),
462       &cancelable_task_tracker_);
463 }
464
465 void BookmarksHandler::OnShortcutFaviconDataAvailable(
466     const BookmarkNode* node,
467     const chrome::FaviconBitmapResult& bitmap_result) {
468   if (!AreModelsLoaded())
469     return;
470
471   SkColor color = SK_ColorWHITE;
472   SkBitmap favicon_bitmap;
473   if (bitmap_result.is_valid()) {
474     color = GetDominantColorForFavicon(bitmap_result.bitmap_data);
475     gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(),
476                           bitmap_result.bitmap_data->size(),
477                           &favicon_bitmap);
478   }
479   TabAndroid* tab = TabAndroid::FromWebContents(web_ui()->GetWebContents());
480   if (tab) {
481     tab->AddShortcutToBookmark(node->url(),
482                                GetTitle(node),
483                                favicon_bitmap, SkColorGetR(color),
484                                SkColorGetG(color), SkColorGetB(color));
485   }
486 }
487
488 const BookmarkNode* BookmarksHandler::GetNodeByID(
489     const base::ListValue* args) const {
490   DCHECK(AreModelsLoaded());
491
492   // Parses a bookmark ID passed back from the NTP.  The IDs differ from the
493   // normal int64 bookmark ID because we prepend a "p" if the ID represents a
494   // partner bookmark, and an "m" if the ID represents a managed bookmark.
495
496   if (!args || args->empty())
497     return NULL;
498
499   std::string string_id;
500   if (!args->GetString(0, &string_id) || string_id.empty()) {
501     NOTREACHED();
502     return NULL;
503   }
504
505   bool is_partner = string_id[0] == 'p';
506   bool is_managed = string_id[0] == 'm';
507
508   if (is_partner || is_managed)
509     string_id = string_id.substr(1);
510
511   int64 id = 0;
512   if (!base::StringToInt64(string_id, &id)) {
513     NOTREACHED();
514     return NULL;
515   }
516
517   if (is_managed)
518     return managed_bookmarks_shim_->GetNodeByID(id);
519
520   if (is_partner)
521     return partner_bookmarks_shim_->GetNodeByID(id);
522
523   return bookmark_model_->GetNodeByID(id);
524 }
525
526 const BookmarkNode* BookmarksHandler::GetParentOf(
527     const BookmarkNode* node) const {
528   DCHECK(AreModelsLoaded());
529   if (node == managed_bookmarks_shim_->GetManagedBookmarksRoot() ||
530       node == partner_bookmarks_shim_->GetPartnerBookmarksRoot()) {
531     return bookmark_model_->mobile_node();
532   }
533
534   return node->parent();
535 }
536
537 base::string16 BookmarksHandler::GetTitle(const BookmarkNode* node) const {
538   DCHECK(AreModelsLoaded());
539   if (partner_bookmarks_shim_->IsPartnerBookmark(node))
540     return partner_bookmarks_shim_->GetTitle(node);
541
542   return node->GetTitle();
543 }
544
545 bool BookmarksHandler::IsReachable(const BookmarkNode* node) const {
546   DCHECK(AreModelsLoaded());
547   if (!partner_bookmarks_shim_->IsPartnerBookmark(node))
548     return true;
549
550   return partner_bookmarks_shim_->IsReachable(node);
551 }
552
553 bool BookmarksHandler::IsEditable(const BookmarkNode* node) const {
554   DCHECK(AreModelsLoaded());
555
556   // Reserved system nodes and managed bookmarks are not editable.
557   // Additionally, bookmark editing may be completely disabled
558   // via a managed preference.
559   if (!node ||
560       (node->type() != BookmarkNode::FOLDER &&
561           node->type() != BookmarkNode::URL)) {
562     return false;
563   }
564
565   const PrefService* pref = Profile::FromBrowserContext(
566       web_ui()->GetWebContents()->GetBrowserContext())->GetPrefs();
567   if (!pref->GetBoolean(prefs::kEditBookmarksEnabled))
568     return false;
569
570   if (partner_bookmarks_shim_->IsPartnerBookmark(node))
571     return true;
572
573   return !managed_bookmarks_shim_->IsManagedBookmark(node);
574 }
575
576 bool BookmarksHandler::IsRoot(const BookmarkNode* node) const {
577   DCHECK(AreModelsLoaded());
578
579   return node->is_root() &&
580          node != partner_bookmarks_shim_->GetPartnerBookmarksRoot() &&
581          node != managed_bookmarks_shim_->GetManagedBookmarksRoot();
582 }