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