- add sources.
[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 ListValue* args) {
146   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
147
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().
153
154   if (!partner_bookmarks_shim_->IsLoaded())
155     return;  // is handled with a PartnerShimLoaded() callback
156
157   const BookmarkNode* node = GetNodeByID(args);
158   if (node)
159     QueryBookmarkFolder(node);
160   else
161     QueryInitialBookmarks();
162 }
163
164 void BookmarksHandler::HandleDeleteBookmark(const ListValue* args) {
165   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
166   const BookmarkNode* node = GetNodeByID(args);
167   if (!node)
168     return;
169
170   if (!IsEditable(node)) {
171     NOTREACHED();
172     return;
173   }
174
175   if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
176     if (partner_bookmarks_shim_->GetPartnerBookmarksRoot() == node)
177       RecordBookmarkAction(BOOKMARK_ACTION_DELETE_ROOT_FOLDER_PARTNER);
178     else
179       RecordBookmarkAction(BOOKMARK_ACTION_DELETE_BOOKMARK_PARTNER);
180     partner_bookmarks_shim_->RemoveBookmark(node);
181     return;
182   }
183
184   const BookmarkNode* parent_node = node->parent();
185   bookmark_model_->Remove(parent_node, parent_node->GetIndexOf(node));
186 }
187
188 void BookmarksHandler::HandleEditBookmark(const ListValue* args) {
189   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190   const BookmarkNode* node = GetNodeByID(args);
191   if (!node)
192     return;
193
194   if (!IsEditable(node)) {
195     NOTREACHED();
196     return;
197   }
198
199   TabAndroid* tab = TabAndroid::FromWebContents(web_ui()->GetWebContents());
200   if (tab) {
201     if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
202       if (partner_bookmarks_shim_->GetPartnerBookmarksRoot() == node)
203         RecordBookmarkAction(BOOKMARK_ACTION_EDIT_ROOT_FOLDER_PARTNER);
204       else
205         RecordBookmarkAction(BOOKMARK_ACTION_EDIT_BOOKMARK_PARTNER);
206     }
207     tab->EditBookmark(node->id(),
208                       GetTitle(node),
209                       node->is_folder(),
210                       partner_bookmarks_shim_->IsPartnerBookmark(node));
211   }
212 }
213
214 std::string BookmarksHandler::GetBookmarkIdForNtp(const BookmarkNode* node) {
215   std::string prefix;
216   if (partner_bookmarks_shim_->IsPartnerBookmark(node))
217     prefix = "p";
218   else if (managed_bookmarks_shim_->IsManagedBookmark(node))
219     prefix = "m";
220   return prefix + Int64ToString(node->id());
221 }
222
223 void BookmarksHandler::SetParentInBookmarksResult(const BookmarkNode* parent,
224                                                   DictionaryValue* result) {
225   result->SetString(kParentIdParam, GetBookmarkIdForNtp(parent));
226 }
227
228 void BookmarksHandler::PopulateBookmark(const BookmarkNode* node,
229                                         ListValue* result) {
230   if (!result)
231     return;
232
233   if (!IsReachable(node))
234     return;
235
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());
242   } else {
243     filler_value->SetBoolean("folder", true);
244   }
245   filler_value->SetString("id", GetBookmarkIdForNtp(node));
246   filler_value->SetString("type", BookmarkTypeAsString(node->type()));
247   result->Append(filler_value);
248 }
249
250 void BookmarksHandler::PopulateBookmarksInFolder(
251     const BookmarkNode* folder,
252     DictionaryValue* result) {
253   DCHECK(partner_bookmarks_shim_ != NULL);
254   DCHECK(IsReachable(folder));
255
256   ListValue* bookmarks = new ListValue();
257
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(),
263                      bookmarks);
264   }
265
266   for (int i = 0; i < folder->child_count(); i++) {
267     const BookmarkNode* bookmark= folder->GetChild(i);
268     PopulateBookmark(bookmark, bookmarks);
269   }
270
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(),
275                      bookmarks);
276   }
277
278   ListValue* folder_hierarchy = new ListValue();
279   const BookmarkNode* parent = GetParentOf(folder);
280
281   while (parent != NULL) {
282     DictionaryValue* hierarchy_entry = new DictionaryValue();
283     if (IsRoot(parent))
284       hierarchy_entry->SetBoolean("root", true);
285
286     hierarchy_entry->SetString("title", GetTitle(parent));
287     hierarchy_entry->SetString("id", GetBookmarkIdForNtp(parent));
288     folder_hierarchy->Append(hierarchy_entry);
289     parent = GetParentOf(parent);
290   }
291
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);
297 }
298
299 void BookmarksHandler::QueryBookmarkFolder(const BookmarkNode* node) {
300   if (node->is_folder() && IsReachable(node)) {
301     DictionaryValue result;
302     PopulateBookmarksInFolder(node, &result);
303     SendResult(result);
304   } else {
305     // If we receive an ID that no longer maps to a bookmark folder, just
306     // return the initial bookmark folder.
307     QueryInitialBookmarks();
308   }
309 }
310
311 void BookmarksHandler::QueryInitialBookmarks() {
312   DictionaryValue result;
313   PopulateBookmarksInFolder(bookmark_model_->mobile_node(), &result);
314   SendResult(result);
315 }
316
317 void BookmarksHandler::SendResult(const DictionaryValue& result) {
318   web_ui()->CallJavascriptFunction("ntp.bookmarks", result);
319 }
320
321 void BookmarksHandler::Loaded(BookmarkModel* model, bool ids_reassigned) {
322   BookmarkModelChanged();
323 }
324
325 void BookmarksHandler::PartnerShimChanged(PartnerBookmarksShim* shim) {
326   BookmarkModelChanged();
327 }
328
329 void BookmarksHandler::PartnerShimLoaded(PartnerBookmarksShim* shim) {
330   BookmarkModelChanged();
331 }
332
333 void BookmarksHandler::ShimBeingDeleted(PartnerBookmarksShim* shim) {
334   partner_bookmarks_shim_ = NULL;
335 }
336
337 void BookmarksHandler::OnManagedBookmarksChanged() {
338   BookmarkModelChanged();
339 }
340
341 void BookmarksHandler::ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {
342   extensive_changes_ = true;
343 }
344
345 void BookmarksHandler::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
346   extensive_changes_ = false;
347   BookmarkModelChanged();
348 }
349
350 void BookmarksHandler::BookmarkNodeRemoved(BookmarkModel* model,
351                                            const BookmarkNode* parent,
352                                            int old_index,
353                                            const BookmarkNode* node) {
354   DictionaryValue result;
355   SetParentInBookmarksResult(parent, &result);
356   result.SetString(kNodeIdParam, Int64ToString(node->id()));
357   NotifyModelChanged(result);
358 }
359
360 void BookmarksHandler::BookmarkAllNodesRemoved(BookmarkModel* model) {
361   if (bookmark_data_requested_ && !extensive_changes_)
362     web_ui()->CallJavascriptFunction("ntp.bookmarkChanged");
363 }
364
365 void BookmarksHandler::BookmarkNodeAdded(
366     BookmarkModel* model, const BookmarkNode* parent, int index) {
367   DictionaryValue result;
368   SetParentInBookmarksResult(parent, &result);
369   NotifyModelChanged(result);
370 }
371
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);
380 }
381
382 void BookmarksHandler::BookmarkModelChanged() {
383   if (bookmark_data_requested_ && !extensive_changes_)
384     web_ui()->CallJavascriptFunction("ntp.bookmarkChanged");
385 }
386
387 void BookmarksHandler::NotifyModelChanged(const DictionaryValue& status) {
388   if (bookmark_data_requested_ && !extensive_changes_)
389     web_ui()->CallJavascriptFunction("ntp.bookmarkChanged", status);
390 }
391
392 void BookmarksHandler::HandleCreateHomeScreenBookmarkShortcut(
393     const ListValue* args) {
394   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
395   Profile* profile = Profile::FromBrowserContext(
396       web_ui()->GetWebContents()->GetBrowserContext());
397   if (!profile)
398     return;
399
400   DCHECK(partner_bookmarks_shim_ != NULL);
401   const BookmarkNode* node = GetNodeByID(args);
402   if (!node)
403     return;
404
405   FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
406       profile, Profile::EXPLICIT_ACCESS);
407   favicon_service->GetRawFaviconForURL(
408       FaviconService::FaviconForURLParams(
409           profile,
410           node->url(),
411           chrome::TOUCH_PRECOMPOSED_ICON | chrome::TOUCH_ICON |
412               chrome::FAVICON,
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),
417                  node),
418       &cancelable_task_tracker_);
419 }
420
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(),
430                           &favicon_bitmap);
431   }
432   TabAndroid* tab = TabAndroid::FromWebContents(web_ui()->GetWebContents());
433   if (tab) {
434     tab->AddShortcutToBookmark(node->url(),
435                                GetTitle(node),
436                                favicon_bitmap, SkColorGetR(color),
437                                SkColorGetG(color), SkColorGetB(color));
438   }
439 }
440
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.
446
447   if (!args || args->empty())
448     return NULL;
449
450   std::string string_id;
451   if (!args->GetString(0, &string_id) || string_id.empty()) {
452     NOTREACHED();
453     return NULL;
454   }
455
456   bool is_partner = string_id[0] == 'p';
457   bool is_managed = string_id[0] == 'm';
458
459   if (is_partner || is_managed)
460     string_id = string_id.substr(1);
461
462   int64 id = 0;
463   if (!base::StringToInt64(string_id, &id)) {
464     NOTREACHED();
465     return NULL;
466   }
467
468   if (is_managed)
469     return managed_bookmarks_shim_->GetNodeByID(id);
470
471   if (is_partner)
472     return partner_bookmarks_shim_->GetNodeByID(id);
473
474   return bookmark_model_->GetNodeByID(id);
475 }
476
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();
482   }
483
484   return node->parent();
485 }
486
487 base::string16 BookmarksHandler::GetTitle(const BookmarkNode* node) const {
488   if (partner_bookmarks_shim_->IsPartnerBookmark(node))
489     return partner_bookmarks_shim_->GetTitle(node);
490
491   return node->GetTitle();
492 }
493
494 bool BookmarksHandler::IsReachable(const BookmarkNode* node) const {
495   if (!partner_bookmarks_shim_->IsPartnerBookmark(node))
496     return true;
497
498   return partner_bookmarks_shim_->IsReachable(node);
499 }
500
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.
505   if (!node ||
506       (node->type() != BookmarkNode::FOLDER &&
507           node->type() != BookmarkNode::URL)) {
508     return false;
509   }
510
511   const PrefService* pref = Profile::FromBrowserContext(
512       web_ui()->GetWebContents()->GetBrowserContext())->GetPrefs();
513   if (!pref->GetBoolean(prefs::kEditBookmarksEnabled))
514     return false;
515
516   if (partner_bookmarks_shim_->IsPartnerBookmark(node))
517     return true;
518
519   return !managed_bookmarks_shim_->IsManagedBookmark(node);
520 }
521
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();
526 }