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.
5 #include "chrome/browser/ui/webui/ntp/android/bookmarks_handler.h"
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"
30 using base::Int64ToString;
31 using content::BrowserThread;
35 static const char* kParentIdParam = "parent_id";
36 static const char* kNodeIdParam = "node_id";
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
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);
55 std::string BookmarkTypeAsString(BookmarkNode::Type type) {
57 case BookmarkNode::URL:
59 case BookmarkNode::FOLDER:
61 case BookmarkNode::BOOKMARK_BAR:
62 return "BOOKMARK_BAR";
63 case BookmarkNode::OTHER_NODE:
65 case BookmarkNode::MOBILE:
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);
83 BookmarksHandler::BookmarksHandler()
84 : bookmark_model_(NULL),
85 partner_bookmarks_shim_(NULL),
86 bookmark_data_requested_(false),
87 extensive_changes_(false) {
90 BookmarksHandler::~BookmarksHandler() {
92 bookmark_model_->RemoveObserver(this);
94 if (partner_bookmarks_shim_)
95 partner_bookmarks_shim_->RemoveObserver(this);
97 if (managed_bookmarks_shim_)
98 managed_bookmarks_shim_->RemoveObserver(this);
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());
107 content::URLDataSource::Add(
108 profile, new FaviconSource(profile, FaviconSource::ANY));
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
116 extensive_changes_ = bookmark_model_->IsDoingExtensiveChanges();
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);
127 managed_bookmarks_shim_.reset(new ManagedBookmarksShim(profile->GetPrefs()));
128 managed_bookmarks_shim_->AddObserver(this);
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)));
145 void BookmarksHandler::HandleGetBookmarks(const ListValue* args) {
146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
148 bookmark_data_requested_ = true;
149 Profile* profile = Profile::FromBrowserContext(
150 web_ui()->GetWebContents()->GetBrowserContext());
151 if (!BookmarkModelFactory::GetForProfile(profile)->loaded())
152 return; // is handled in Loaded().
154 if (!partner_bookmarks_shim_->IsLoaded())
155 return; // is handled with a PartnerShimLoaded() callback
157 const BookmarkNode* node = GetNodeByID(args);
159 QueryBookmarkFolder(node);
161 QueryInitialBookmarks();
164 void BookmarksHandler::HandleDeleteBookmark(const ListValue* args) {
165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
166 const BookmarkNode* node = GetNodeByID(args);
170 if (!IsEditable(node)) {
175 if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
176 if (partner_bookmarks_shim_->GetPartnerBookmarksRoot() == node)
177 RecordBookmarkAction(BOOKMARK_ACTION_DELETE_ROOT_FOLDER_PARTNER);
179 RecordBookmarkAction(BOOKMARK_ACTION_DELETE_BOOKMARK_PARTNER);
180 partner_bookmarks_shim_->RemoveBookmark(node);
184 const BookmarkNode* parent_node = node->parent();
185 bookmark_model_->Remove(parent_node, parent_node->GetIndexOf(node));
188 void BookmarksHandler::HandleEditBookmark(const ListValue* args) {
189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190 const BookmarkNode* node = GetNodeByID(args);
194 if (!IsEditable(node)) {
199 TabAndroid* tab = TabAndroid::FromWebContents(web_ui()->GetWebContents());
201 if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
202 if (partner_bookmarks_shim_->GetPartnerBookmarksRoot() == node)
203 RecordBookmarkAction(BOOKMARK_ACTION_EDIT_ROOT_FOLDER_PARTNER);
205 RecordBookmarkAction(BOOKMARK_ACTION_EDIT_BOOKMARK_PARTNER);
207 tab->EditBookmark(node->id(),
210 partner_bookmarks_shim_->IsPartnerBookmark(node));
214 std::string BookmarksHandler::GetBookmarkIdForNtp(const BookmarkNode* node) {
216 if (partner_bookmarks_shim_->IsPartnerBookmark(node))
218 else if (managed_bookmarks_shim_->IsManagedBookmark(node))
220 return prefix + Int64ToString(node->id());
223 void BookmarksHandler::SetParentInBookmarksResult(const BookmarkNode* parent,
224 DictionaryValue* result) {
225 result->SetString(kParentIdParam, GetBookmarkIdForNtp(parent));
228 void BookmarksHandler::PopulateBookmark(const BookmarkNode* node,
233 if (!IsReachable(node))
236 DictionaryValue* filler_value = new DictionaryValue();
237 filler_value->SetString("title", GetTitle(node));
238 filler_value->SetBoolean("editable", IsEditable(node));
239 if (node->is_url()) {
240 filler_value->SetBoolean("folder", false);
241 filler_value->SetString("url", node->url().spec());
243 filler_value->SetBoolean("folder", true);
245 filler_value->SetString("id", GetBookmarkIdForNtp(node));
246 filler_value->SetString("type", BookmarkTypeAsString(node->type()));
247 result->Append(filler_value);
250 void BookmarksHandler::PopulateBookmarksInFolder(
251 const BookmarkNode* folder,
252 DictionaryValue* result) {
253 DCHECK(partner_bookmarks_shim_ != NULL);
254 DCHECK(IsReachable(folder));
256 ListValue* bookmarks = new ListValue();
258 // If this is the Mobile bookmarks folder then add the "Managed bookmarks"
259 // folder first, so that it's the first entry.
260 if (bookmark_model_ && folder == bookmark_model_->mobile_node() &&
261 managed_bookmarks_shim_->HasManagedBookmarks()) {
262 PopulateBookmark(managed_bookmarks_shim_->GetManagedBookmarksRoot(),
266 for (int i = 0; i < folder->child_count(); i++) {
267 const BookmarkNode* bookmark= folder->GetChild(i);
268 PopulateBookmark(bookmark, bookmarks);
271 // Make sure we iterate over the partner's attach point
272 if (bookmark_model_ && folder == bookmark_model_->mobile_node() &&
273 partner_bookmarks_shim_->HasPartnerBookmarks()) {
274 PopulateBookmark(partner_bookmarks_shim_->GetPartnerBookmarksRoot(),
278 ListValue* folder_hierarchy = new ListValue();
279 const BookmarkNode* parent = GetParentOf(folder);
281 while (parent != NULL) {
282 DictionaryValue* hierarchy_entry = new DictionaryValue();
284 hierarchy_entry->SetBoolean("root", true);
286 hierarchy_entry->SetString("title", GetTitle(parent));
287 hierarchy_entry->SetString("id", GetBookmarkIdForNtp(parent));
288 folder_hierarchy->Append(hierarchy_entry);
289 parent = GetParentOf(parent);
292 result->SetString("title", GetTitle(folder));
293 result->SetString("id", GetBookmarkIdForNtp(folder));
294 result->SetBoolean("root", IsRoot(folder));
295 result->Set("bookmarks", bookmarks);
296 result->Set("hierarchy", folder_hierarchy);
299 void BookmarksHandler::QueryBookmarkFolder(const BookmarkNode* node) {
300 if (node->is_folder() && IsReachable(node)) {
301 DictionaryValue result;
302 PopulateBookmarksInFolder(node, &result);
305 // If we receive an ID that no longer maps to a bookmark folder, just
306 // return the initial bookmark folder.
307 QueryInitialBookmarks();
311 void BookmarksHandler::QueryInitialBookmarks() {
312 DictionaryValue result;
313 PopulateBookmarksInFolder(bookmark_model_->mobile_node(), &result);
317 void BookmarksHandler::SendResult(const DictionaryValue& result) {
318 web_ui()->CallJavascriptFunction("ntp.bookmarks", result);
321 void BookmarksHandler::Loaded(BookmarkModel* model, bool ids_reassigned) {
322 BookmarkModelChanged();
325 void BookmarksHandler::PartnerShimChanged(PartnerBookmarksShim* shim) {
326 BookmarkModelChanged();
329 void BookmarksHandler::PartnerShimLoaded(PartnerBookmarksShim* shim) {
330 BookmarkModelChanged();
333 void BookmarksHandler::ShimBeingDeleted(PartnerBookmarksShim* shim) {
334 partner_bookmarks_shim_ = NULL;
337 void BookmarksHandler::OnManagedBookmarksChanged() {
338 BookmarkModelChanged();
341 void BookmarksHandler::ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {
342 extensive_changes_ = true;
345 void BookmarksHandler::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
346 extensive_changes_ = false;
347 BookmarkModelChanged();
350 void BookmarksHandler::BookmarkNodeRemoved(BookmarkModel* model,
351 const BookmarkNode* parent,
353 const BookmarkNode* node) {
354 DictionaryValue result;
355 SetParentInBookmarksResult(parent, &result);
356 result.SetString(kNodeIdParam, Int64ToString(node->id()));
357 NotifyModelChanged(result);
360 void BookmarksHandler::BookmarkAllNodesRemoved(BookmarkModel* model) {
361 if (bookmark_data_requested_ && !extensive_changes_)
362 web_ui()->CallJavascriptFunction("ntp.bookmarkChanged");
365 void BookmarksHandler::BookmarkNodeAdded(
366 BookmarkModel* model, const BookmarkNode* parent, int index) {
367 DictionaryValue result;
368 SetParentInBookmarksResult(parent, &result);
369 NotifyModelChanged(result);
372 void BookmarksHandler::BookmarkNodeChanged(BookmarkModel* model,
373 const BookmarkNode* node) {
374 DCHECK(partner_bookmarks_shim_);
375 DCHECK(!partner_bookmarks_shim_->IsPartnerBookmark(node));
376 DictionaryValue result;
377 SetParentInBookmarksResult(node->parent(), &result);
378 result.SetString(kNodeIdParam, Int64ToString(node->id()));
379 NotifyModelChanged(result);
382 void BookmarksHandler::BookmarkModelChanged() {
383 if (bookmark_data_requested_ && !extensive_changes_)
384 web_ui()->CallJavascriptFunction("ntp.bookmarkChanged");
387 void BookmarksHandler::NotifyModelChanged(const DictionaryValue& status) {
388 if (bookmark_data_requested_ && !extensive_changes_)
389 web_ui()->CallJavascriptFunction("ntp.bookmarkChanged", status);
392 void BookmarksHandler::HandleCreateHomeScreenBookmarkShortcut(
393 const ListValue* args) {
394 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
395 Profile* profile = Profile::FromBrowserContext(
396 web_ui()->GetWebContents()->GetBrowserContext());
400 DCHECK(partner_bookmarks_shim_ != NULL);
401 const BookmarkNode* node = GetNodeByID(args);
405 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
406 profile, Profile::EXPLICIT_ACCESS);
407 favicon_service->GetRawFaviconForURL(
408 FaviconService::FaviconForURLParams(
411 chrome::TOUCH_PRECOMPOSED_ICON | chrome::TOUCH_ICON |
413 0), // request the largest icon.
414 ui::SCALE_FACTOR_100P, // density doesn't matter for the largest icon.
415 base::Bind(&BookmarksHandler::OnShortcutFaviconDataAvailable,
416 base::Unretained(this),
418 &cancelable_task_tracker_);
421 void BookmarksHandler::OnShortcutFaviconDataAvailable(
422 const BookmarkNode* node,
423 const chrome::FaviconBitmapResult& bitmap_result) {
424 SkColor color = SK_ColorWHITE;
425 SkBitmap favicon_bitmap;
426 if (bitmap_result.is_valid()) {
427 color = GetDominantColorForFavicon(bitmap_result.bitmap_data);
428 gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(),
429 bitmap_result.bitmap_data->size(),
432 TabAndroid* tab = TabAndroid::FromWebContents(web_ui()->GetWebContents());
434 tab->AddShortcutToBookmark(node->url(),
436 favicon_bitmap, SkColorGetR(color),
437 SkColorGetG(color), SkColorGetB(color));
441 const BookmarkNode* BookmarksHandler::GetNodeByID(
442 const base::ListValue* args) const {
443 // Parses a bookmark ID passed back from the NTP. The IDs differ from the
444 // normal int64 bookmark ID because we prepend a "p" if the ID represents a
445 // partner bookmark, and an "m" if the ID represents a managed bookmark.
447 if (!args || args->empty())
450 std::string string_id;
451 if (!args->GetString(0, &string_id) || string_id.empty()) {
456 bool is_partner = string_id[0] == 'p';
457 bool is_managed = string_id[0] == 'm';
459 if (is_partner || is_managed)
460 string_id = string_id.substr(1);
463 if (!base::StringToInt64(string_id, &id)) {
469 return managed_bookmarks_shim_->GetNodeByID(id);
472 return partner_bookmarks_shim_->GetNodeByID(id);
474 return bookmark_model_->GetNodeByID(id);
477 const BookmarkNode* BookmarksHandler::GetParentOf(
478 const BookmarkNode* node) const {
479 if (node == managed_bookmarks_shim_->GetManagedBookmarksRoot() ||
480 node == partner_bookmarks_shim_->GetPartnerBookmarksRoot()) {
481 return bookmark_model_->mobile_node();
484 return node->parent();
487 base::string16 BookmarksHandler::GetTitle(const BookmarkNode* node) const {
488 if (partner_bookmarks_shim_->IsPartnerBookmark(node))
489 return partner_bookmarks_shim_->GetTitle(node);
491 return node->GetTitle();
494 bool BookmarksHandler::IsReachable(const BookmarkNode* node) const {
495 if (!partner_bookmarks_shim_->IsPartnerBookmark(node))
498 return partner_bookmarks_shim_->IsReachable(node);
501 bool BookmarksHandler::IsEditable(const BookmarkNode* node) const {
502 // Reserved system nodes and managed bookmarks are not editable.
503 // Additionally, bookmark editing may be completely disabled
504 // via a managed preference.
506 (node->type() != BookmarkNode::FOLDER &&
507 node->type() != BookmarkNode::URL)) {
511 const PrefService* pref = Profile::FromBrowserContext(
512 web_ui()->GetWebContents()->GetBrowserContext())->GetPrefs();
513 if (!pref->GetBoolean(prefs::kEditBookmarksEnabled))
516 if (partner_bookmarks_shim_->IsPartnerBookmark(node))
519 return !managed_bookmarks_shim_->IsManagedBookmark(node);
522 bool BookmarksHandler::IsRoot(const BookmarkNode* node) const {
523 return node->is_root() &&
524 node != partner_bookmarks_shim_->GetPartnerBookmarksRoot() &&
525 node != managed_bookmarks_shim_->GetManagedBookmarksRoot();