Home | History | Annotate | Download | only in app_list
      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/app_list/apps_model_builder.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/auto_reset.h"
     10 #include "base/prefs/pref_service.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/extensions/extension_prefs.h"
     13 #include "chrome/browser/extensions/extension_service.h"
     14 #include "chrome/browser/extensions/extension_sorting.h"
     15 #include "chrome/browser/extensions/extension_system.h"
     16 #include "chrome/browser/extensions/install_tracker.h"
     17 #include "chrome/browser/extensions/install_tracker_factory.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/browser/ui/app_list/extension_app_item.h"
     20 #include "chrome/common/extensions/extension.h"
     21 #include "chrome/common/extensions/extension_constants.h"
     22 #include "chrome/common/pref_names.h"
     23 #include "content/public/browser/notification_service.h"
     24 #include "ui/gfx/image/image_skia.h"
     25 
     26 using extensions::Extension;
     27 
     28 namespace {
     29 
     30 bool AppPrecedes(const ExtensionAppItem* app1, const ExtensionAppItem* app2) {
     31   const syncer::StringOrdinal& page1 = app1->GetPageOrdinal();
     32   const syncer::StringOrdinal& page2 = app2->GetPageOrdinal();
     33   if (page1.LessThan(page2))
     34     return true;
     35 
     36   if (page1.Equals(page2))
     37     return app1->GetAppLaunchOrdinal().LessThan(app2->GetAppLaunchOrdinal());
     38 
     39   return false;
     40 }
     41 
     42 bool ShouldDisplayInAppLauncher(Profile* profile,
     43                                 scoped_refptr<const Extension> app) {
     44   // If it's the web store, check the policy.
     45   bool blocked_by_policy =
     46       (app->id() == extension_misc::kWebStoreAppId ||
     47        app->id() == extension_misc::kEnterpriseWebStoreAppId) &&
     48       profile->GetPrefs()->GetBoolean(prefs::kHideWebStoreIcon);
     49   return app->ShouldDisplayInAppLauncher() && !blocked_by_policy;
     50 }
     51 
     52 }  // namespace
     53 
     54 AppsModelBuilder::AppsModelBuilder(Profile* profile,
     55                                    app_list::AppListModel::Apps* model,
     56                                    AppListControllerDelegate* controller)
     57     : profile_(profile),
     58       controller_(controller),
     59       model_(model),
     60       highlighted_app_pending_(false),
     61       ignore_changes_(false),
     62       tracker_(extensions::InstallTrackerFactory::GetForProfile(profile_)) {
     63   model_->AddObserver(this);
     64 }
     65 
     66 AppsModelBuilder::~AppsModelBuilder() {
     67   OnShutdown();
     68   model_->RemoveObserver(this);
     69 }
     70 
     71 void AppsModelBuilder::Build() {
     72   DCHECK(model_ && model_->item_count() == 0);
     73 
     74   PopulateApps();
     75   UpdateHighlight();
     76 
     77   // Start observing after model is built.
     78   tracker_->AddObserver(this);
     79 }
     80 
     81 void AppsModelBuilder::OnBeginExtensionInstall(
     82     const std::string& extension_id,
     83     const std::string& extension_name,
     84     const gfx::ImageSkia& installing_icon,
     85     bool is_app,
     86     bool is_platform_app) {
     87   if (!is_app)
     88     return;
     89   InsertApp(new ExtensionAppItem(profile_,
     90                                  extension_id,
     91                                  controller_,
     92                                  extension_name,
     93                                  installing_icon,
     94                                  is_platform_app));
     95   SetHighlightedApp(extension_id);
     96 }
     97 
     98 void AppsModelBuilder::OnDownloadProgress(const std::string& extension_id,
     99                                           int percent_downloaded) {
    100   int i = FindApp(extension_id);
    101   if (i == -1)
    102     return;
    103   GetAppAt(i)->SetPercentDownloaded(percent_downloaded);
    104 }
    105 
    106 void AppsModelBuilder::OnInstallFailure(const std::string& extension_id) {
    107   int i = FindApp(extension_id);
    108   if (i == -1)
    109     return;
    110   model_->DeleteAt(i);
    111 }
    112 
    113 void AppsModelBuilder::OnExtensionLoaded(const Extension* extension) {
    114   if (!extension->ShouldDisplayInAppLauncher())
    115     return;
    116 
    117   const int existing_index = FindApp(extension->id());
    118   if (existing_index != -1) {
    119     GetAppAt(existing_index)->Reload();
    120     return;
    121   }
    122 
    123   InsertApp(new ExtensionAppItem(profile_,
    124                                  extension->id(),
    125                                  controller_,
    126                                  "",
    127                                  gfx::ImageSkia(),
    128                                  extension->is_platform_app()));
    129   UpdateHighlight();
    130 }
    131 
    132 void AppsModelBuilder::OnExtensionUnloaded(const Extension* extension) {
    133   int index = FindApp(extension->id());
    134   if (index < 0)
    135     return;
    136   GetAppAt(index)->UpdateIcon();
    137 }
    138 
    139 void AppsModelBuilder::OnExtensionUninstalled(const Extension* extension) {
    140   int index = FindApp(extension->id());
    141   if (index < 0)
    142     return;
    143   model_->DeleteAt(index);
    144 }
    145 
    146 void AppsModelBuilder::OnAppsReordered() {
    147   ResortApps();
    148 }
    149 
    150 void AppsModelBuilder::OnAppInstalledToAppList(
    151     const std::string& extension_id) {
    152   SetHighlightedApp(extension_id);
    153 }
    154 
    155 void AppsModelBuilder::OnShutdown() {
    156   if (tracker_) {
    157     tracker_->RemoveObserver(this);
    158     tracker_ = NULL;
    159   }
    160 }
    161 
    162 void AppsModelBuilder::AddApps(const ExtensionSet* extensions, Apps* apps) {
    163   for (ExtensionSet::const_iterator app = extensions->begin();
    164        app != extensions->end(); ++app) {
    165     if (ShouldDisplayInAppLauncher(profile_, *app))
    166       apps->push_back(new ExtensionAppItem(profile_,
    167                                            (*app)->id(),
    168                                            controller_,
    169                                            "",
    170                                            gfx::ImageSkia(),
    171                                            (*app)->is_platform_app()));
    172   }
    173 }
    174 
    175 void AppsModelBuilder::PopulateApps() {
    176   ExtensionService* service =
    177       extensions::ExtensionSystem::Get(profile_)->extension_service();
    178   if (!service)
    179     return;
    180 
    181   Apps apps;
    182   AddApps(service->extensions(), &apps);
    183   AddApps(service->disabled_extensions(), &apps);
    184   AddApps(service->terminated_extensions(), &apps);
    185 
    186   if (apps.empty())
    187     return;
    188 
    189   service->extension_prefs()->extension_sorting()->FixNTPOrdinalCollisions();
    190   std::sort(apps.begin(), apps.end(), &AppPrecedes);
    191 
    192   for (size_t i = 0; i < apps.size(); ++i)
    193     model_->Add(apps[i]);
    194 }
    195 
    196 void AppsModelBuilder::ResortApps() {
    197   // Scan app items in |model_| and put the apps that do not have valid ordinals
    198   // into |invalid_ordinal_apps|. This is needed to handle uninstalling a
    199   // terminated app case, where there is no unload notification and uninstall
    200   // notification comes in after the app's ordinals are cleared.
    201   // See http://crbug.com/256749.
    202   Apps apps;
    203   Apps invalid_ordinal_apps;
    204   for (size_t i = 0; i < model_->item_count(); ++i) {
    205     ExtensionAppItem* app = GetAppAt(i);
    206     if (app->GetPageOrdinal().IsValid() && app->GetAppLaunchOrdinal().IsValid())
    207       apps.push_back(app);
    208     else
    209       invalid_ordinal_apps.push_back(app);
    210   }
    211 
    212   std::sort(apps.begin(), apps.end(), &AppPrecedes);
    213   apps.insert(
    214       apps.end(), invalid_ordinal_apps.begin(), invalid_ordinal_apps.end());
    215 
    216   base::AutoReset<bool> auto_reset(&ignore_changes_, true);
    217 
    218   // Adjusts the order of apps as needed in |model_| based on |apps|.
    219   for (size_t i = 0; i < apps.size(); ++i) {
    220     ExtensionAppItem* app_item = apps[i];
    221 
    222     // Finds |app_item| in remaining unsorted part in |model_|.
    223     for (size_t j = i; j < model_->item_count(); ++j) {
    224       if (GetAppAt(j) == app_item) {
    225         model_->Move(j, i);
    226         break;
    227       }
    228     }
    229   }
    230 }
    231 
    232 void AppsModelBuilder::InsertApp(ExtensionAppItem* app) {
    233   DCHECK(model_);
    234 
    235   size_t start = 0;
    236   size_t end = model_->item_count();
    237   while (start < end) {
    238     size_t mid = (start + end) / 2;
    239 
    240     if (AppPrecedes(GetAppAt(mid), app))
    241       start = mid + 1;
    242     else
    243       end = mid;
    244   }
    245   model_->AddAt(start, app);
    246 }
    247 
    248 int AppsModelBuilder::FindApp(const std::string& app_id) {
    249   DCHECK(model_);
    250 
    251   for (size_t i = 0; i < model_->item_count(); ++i) {
    252     if (GetAppAt(i)->extension_id() == app_id)
    253       return i;
    254   }
    255 
    256   return -1;
    257 }
    258 
    259 void AppsModelBuilder::SetHighlightedApp(const std::string& extension_id) {
    260   if (extension_id == highlight_app_id_)
    261     return;
    262   ExtensionAppItem* old_app = GetApp(highlight_app_id_);
    263   if (old_app)
    264     old_app->SetHighlighted(false);
    265   highlight_app_id_ = extension_id;
    266   ExtensionAppItem* new_app = GetApp(highlight_app_id_);
    267   highlighted_app_pending_ = !new_app;
    268   if (new_app)
    269     new_app->SetHighlighted(true);
    270 }
    271 
    272 ExtensionAppItem* AppsModelBuilder::GetApp(
    273     const std::string& extension_id) {
    274   DCHECK(model_);
    275   if (extension_id.empty())
    276     return NULL;
    277 
    278   int index = FindApp(extension_id);
    279   if (index == -1)
    280     return NULL;
    281   return GetAppAt(index);
    282 }
    283 
    284 void AppsModelBuilder::UpdateHighlight() {
    285   DCHECK(model_);
    286   if (!highlighted_app_pending_ || highlight_app_id_.empty())
    287     return;
    288 
    289   int index = FindApp(highlight_app_id_);
    290   if (index == -1)
    291     return;
    292 
    293   model_->GetItemAt(index)->SetHighlighted(true);
    294   highlighted_app_pending_ = false;
    295 }
    296 
    297 ExtensionAppItem* AppsModelBuilder::GetAppAt(size_t index) {
    298   DCHECK_LT(index, model_->item_count());
    299   ChromeAppListItem* item =
    300       static_cast<ChromeAppListItem*>(model_->GetItemAt(index));
    301   DCHECK_EQ(item->type(), ChromeAppListItem::TYPE_APP);
    302 
    303   return static_cast<ExtensionAppItem*>(item);
    304 }
    305 
    306 void AppsModelBuilder::ListItemsAdded(size_t start, size_t count) {
    307 }
    308 
    309 void AppsModelBuilder::ListItemsRemoved(size_t start, size_t count) {
    310 }
    311 
    312 void AppsModelBuilder::ListItemMoved(size_t index, size_t target_index) {
    313   if (ignore_changes_)
    314     return;
    315 
    316   ExtensionAppItem* prev = target_index > 0 ? GetAppAt(target_index - 1) : NULL;
    317   ExtensionAppItem* next = target_index + 1 < model_->item_count() ?
    318       GetAppAt(target_index + 1) : NULL;
    319   GetAppAt(target_index)->Move(prev, next);
    320 }
    321 
    322 void AppsModelBuilder::ListItemsChanged(size_t start, size_t count) {
    323   NOTREACHED();
    324 }
    325