Home | History | Annotate | Download | only in android
      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