Home | History | Annotate | Download | only in background
      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 <algorithm>
      6 #include <string>
      7 #include <vector>
      8 
      9 #include "base/base_paths.h"
     10 #include "base/bind.h"
     11 #include "base/command_line.h"
     12 #include "base/logging.h"
     13 #include "base/prefs/pref_registry_simple.h"
     14 #include "base/prefs/pref_service.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "chrome/app/chrome_command_ids.h"
     17 #include "chrome/browser/background/background_application_list_model.h"
     18 #include "chrome/browser/background/background_mode_manager.h"
     19 #include "chrome/browser/browser_process.h"
     20 #include "chrome/browser/browser_shutdown.h"
     21 #include "chrome/browser/chrome_notification_types.h"
     22 #include "chrome/browser/extensions/extension_service.h"
     23 #include "chrome/browser/lifetime/application_lifetime.h"
     24 #include "chrome/browser/profiles/profile.h"
     25 #include "chrome/browser/profiles/profile_info_cache.h"
     26 #include "chrome/browser/profiles/profile_manager.h"
     27 #include "chrome/browser/status_icons/status_icon.h"
     28 #include "chrome/browser/status_icons/status_tray.h"
     29 #include "chrome/browser/ui/browser.h"
     30 #include "chrome/browser/ui/browser_commands.h"
     31 #include "chrome/browser/ui/browser_finder.h"
     32 #include "chrome/browser/ui/browser_list.h"
     33 #include "chrome/browser/ui/chrome_pages.h"
     34 #include "chrome/browser/ui/extensions/application_launch.h"
     35 #include "chrome/browser/ui/host_desktop.h"
     36 #include "chrome/common/chrome_constants.h"
     37 #include "chrome/common/chrome_switches.h"
     38 #include "chrome/common/extensions/extension_constants.h"
     39 #include "chrome/common/extensions/manifest_url_handler.h"
     40 #include "chrome/common/pref_names.h"
     41 #include "content/public/browser/notification_service.h"
     42 #include "content/public/browser/user_metrics.h"
     43 #include "extensions/browser/extension_system.h"
     44 #include "extensions/common/extension.h"
     45 #include "extensions/common/permissions/permission_set.h"
     46 #include "grit/chrome_unscaled_resources.h"
     47 #include "grit/chromium_strings.h"
     48 #include "grit/generated_resources.h"
     49 #include "ui/base/l10n/l10n_util.h"
     50 #include "ui/base/resource/resource_bundle.h"
     51 
     52 using base::UserMetricsAction;
     53 using extensions::Extension;
     54 using extensions::UpdatedExtensionPermissionsInfo;
     55 
     56 namespace {
     57 const int kInvalidExtensionIndex = -1;
     58 }
     59 
     60 BackgroundModeManager::BackgroundModeData::BackgroundModeData(
     61     Profile* profile,
     62     CommandIdExtensionVector* command_id_extension_vector)
     63     : applications_(new BackgroundApplicationListModel(profile)),
     64       profile_(profile),
     65       command_id_extension_vector_(command_id_extension_vector) {
     66 }
     67 
     68 BackgroundModeManager::BackgroundModeData::~BackgroundModeData() {
     69 }
     70 
     71 ///////////////////////////////////////////////////////////////////////////////
     72 //  BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
     73 void BackgroundModeManager::BackgroundModeData::ExecuteCommand(
     74     int command_id,
     75     int event_flags) {
     76   switch (command_id) {
     77     case IDC_MinimumLabelValue:
     78       // Do nothing. This is just a label.
     79       break;
     80     default:
     81       // Launch the app associated with this Command ID.
     82       int extension_index = command_id_extension_vector_->at(command_id);
     83       if (extension_index != kInvalidExtensionIndex) {
     84         const Extension* extension =
     85             applications_->GetExtension(extension_index);
     86         BackgroundModeManager::LaunchBackgroundApplication(profile_, extension);
     87       }
     88       break;
     89   }
     90 }
     91 
     92 Browser* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
     93   chrome::HostDesktopType host_desktop_type = chrome::GetActiveDesktop();
     94   Browser* browser = chrome::FindLastActiveWithProfile(profile_,
     95                                                        host_desktop_type);
     96   return browser ? browser : chrome::OpenEmptyWindow(profile_,
     97                                                      host_desktop_type);
     98 }
     99 
    100 int BackgroundModeManager::BackgroundModeData::GetBackgroundAppCount() const {
    101   return applications_->size();
    102 }
    103 
    104 void BackgroundModeManager::BackgroundModeData::BuildProfileMenu(
    105     StatusIconMenuModel* menu,
    106     StatusIconMenuModel* containing_menu) {
    107   int position = 0;
    108   // When there are no background applications, we want to display
    109   // just a label stating that none are running.
    110   if (applications_->size() < 1) {
    111     menu->AddItemWithStringId(IDC_MinimumLabelValue,
    112                               IDS_BACKGROUND_APP_NOT_INSTALLED);
    113     menu->SetCommandIdEnabled(IDC_MinimumLabelValue, false);
    114   } else {
    115     for (extensions::ExtensionList::const_iterator cursor =
    116              applications_->begin();
    117          cursor != applications_->end();
    118          ++cursor, ++position) {
    119       const gfx::ImageSkia* icon = applications_->GetIcon(cursor->get());
    120       DCHECK(position == applications_->GetPosition(cursor->get()));
    121       const std::string& name = (*cursor)->name();
    122       int command_id = command_id_extension_vector_->size();
    123       // Check that the command ID is within the dynamic range.
    124       DCHECK(command_id < IDC_MinimumLabelValue);
    125       command_id_extension_vector_->push_back(position);
    126       menu->AddItem(command_id, base::UTF8ToUTF16(name));
    127       if (icon)
    128         menu->SetIcon(menu->GetItemCount() - 1, gfx::Image(*icon));
    129 
    130       // Component extensions with background that do not have an options page
    131       // will cause this menu item to go to the extensions page with an
    132       // absent component extension.
    133       //
    134       // Ideally, we would remove this item, but this conflicts with the user
    135       // model where this menu shows the extensions with background.
    136       //
    137       // The compromise is to disable the item, avoiding the non-actionable
    138       // navigate to the extensions page and preserving the user model.
    139       if ((*cursor)->location() == extensions::Manifest::COMPONENT) {
    140         GURL options_page = extensions::ManifestURL::GetOptionsPage(*cursor);
    141         if (!options_page.is_valid())
    142           menu->SetCommandIdEnabled(command_id, false);
    143       }
    144     }
    145   }
    146   if (containing_menu) {
    147     int menu_command_id = command_id_extension_vector_->size();
    148     // Check that the command ID is within the dynamic range.
    149     DCHECK(menu_command_id < IDC_MinimumLabelValue);
    150     command_id_extension_vector_->push_back(kInvalidExtensionIndex);
    151     containing_menu->AddSubMenu(menu_command_id, name_, menu);
    152   }
    153 }
    154 
    155 void BackgroundModeManager::BackgroundModeData::SetName(
    156     const base::string16& new_profile_name) {
    157   name_ = new_profile_name;
    158 }
    159 
    160 base::string16 BackgroundModeManager::BackgroundModeData::name() {
    161   return name_;
    162 }
    163 
    164 // static
    165 bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
    166     const BackgroundModeData* bmd1,
    167     const BackgroundModeData* bmd2) {
    168   return bmd1->name_ < bmd2->name_;
    169 }
    170 
    171 
    172 ///////////////////////////////////////////////////////////////////////////////
    173 //  BackgroundModeManager, public
    174 BackgroundModeManager::BackgroundModeManager(
    175     CommandLine* command_line,
    176     ProfileInfoCache* profile_cache)
    177     : profile_cache_(profile_cache),
    178       status_tray_(NULL),
    179       status_icon_(NULL),
    180       context_menu_(NULL),
    181       in_background_mode_(false),
    182       keep_alive_for_startup_(false),
    183       keep_alive_for_test_(false),
    184       background_mode_suspended_(false),
    185       keeping_alive_(false) {
    186   // We should never start up if there is no browser process or if we are
    187   // currently quitting.
    188   CHECK(g_browser_process != NULL);
    189   CHECK(!browser_shutdown::IsTryingToQuit());
    190 
    191   // Add self as an observer for the profile info cache so we know when profiles
    192   // are deleted and their names change.
    193   profile_cache_->AddObserver(this);
    194 
    195   // Listen for the background mode preference changing.
    196   if (g_browser_process->local_state()) {  // Skip for unit tests
    197     pref_registrar_.Init(g_browser_process->local_state());
    198     pref_registrar_.Add(
    199         prefs::kBackgroundModeEnabled,
    200         base::Bind(&BackgroundModeManager::OnBackgroundModeEnabledPrefChanged,
    201                    base::Unretained(this)));
    202   }
    203 
    204   // Keep the browser alive until extensions are done loading - this is needed
    205   // by the --no-startup-window flag. We want to stay alive until we load
    206   // extensions, at which point we should either run in background mode (if
    207   // there are background apps) or exit if there are none.
    208   if (command_line->HasSwitch(switches::kNoStartupWindow)) {
    209     keep_alive_for_startup_ = true;
    210     chrome::IncrementKeepAliveCount();
    211   } else {
    212     // Otherwise, start with background mode suspended in case we're launching
    213     // in a mode that doesn't open a browser window. It will be resumed when the
    214     // first browser window is opened.
    215     SuspendBackgroundMode();
    216   }
    217 
    218   // If the -keep-alive-for-test flag is passed, then always keep chrome running
    219   // in the background until the user explicitly terminates it.
    220   if (command_line->HasSwitch(switches::kKeepAliveForTest))
    221     keep_alive_for_test_ = true;
    222 
    223   if (ShouldBeInBackgroundMode())
    224     StartBackgroundMode();
    225 
    226   // Listen for the application shutting down so we can decrement our KeepAlive
    227   // count.
    228   registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
    229                  content::NotificationService::AllSources());
    230   BrowserList::AddObserver(this);
    231 }
    232 
    233 BackgroundModeManager::~BackgroundModeManager() {
    234   // Remove ourselves from the application observer list (only needed by unit
    235   // tests since APP_TERMINATING is what does this in a real running system).
    236   for (BackgroundModeInfoMap::iterator it =
    237        background_mode_data_.begin();
    238        it != background_mode_data_.end();
    239        ++it) {
    240     it->second->applications_->RemoveObserver(this);
    241   }
    242   BrowserList::RemoveObserver(this);
    243 
    244   // We're going away, so exit background mode (does nothing if we aren't in
    245   // background mode currently). This is primarily needed for unit tests,
    246   // because in an actual running system we'd get an APP_TERMINATING
    247   // notification before being destroyed.
    248   EndBackgroundMode();
    249 }
    250 
    251 // static
    252 void BackgroundModeManager::RegisterPrefs(PrefRegistrySimple* registry) {
    253 #if defined(OS_MACOSX)
    254   registry->RegisterBooleanPref(prefs::kUserRemovedLoginItem, false);
    255   registry->RegisterBooleanPref(prefs::kChromeCreatedLoginItem, false);
    256   registry->RegisterBooleanPref(prefs::kMigratedLoginItemPref, false);
    257 #endif
    258   registry->RegisterBooleanPref(prefs::kBackgroundModeEnabled, true);
    259 }
    260 
    261 
    262 void BackgroundModeManager::RegisterProfile(Profile* profile) {
    263   // We don't want to register multiple times for one profile.
    264   DCHECK(background_mode_data_.find(profile) == background_mode_data_.end());
    265   BackgroundModeInfo bmd(new BackgroundModeData(profile,
    266                                                 &command_id_extension_vector_));
    267   background_mode_data_[profile] = bmd;
    268 
    269   // Initially set the name for this background mode data.
    270   size_t index = profile_cache_->GetIndexOfProfileWithPath(profile->GetPath());
    271   base::string16 name = l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME);
    272   if (index != std::string::npos)
    273     name = profile_cache_->GetNameOfProfileAtIndex(index);
    274   bmd->SetName(name);
    275 
    276   // Listen for when extensions are loaded or add the background permission so
    277   // we can display a "background app installed" notification and enter
    278   // "launch on login" mode on the Mac.
    279   registrar_.Add(this,
    280                  chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
    281                  content::Source<Profile>(profile));
    282   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
    283                  content::Source<Profile>(profile));
    284 
    285 
    286   // Check for the presence of background apps after all extensions have been
    287   // loaded, to handle the case where an extension has been manually removed
    288   // while Chrome was not running.
    289   registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
    290                  content::Source<Profile>(profile));
    291 
    292   bmd->applications_->AddObserver(this);
    293 
    294   // If we're adding a new profile and running in multi-profile mode, this new
    295   // profile should be added to the status icon if one currently exists.
    296   if (in_background_mode_ && status_icon_)
    297     UpdateStatusTrayIconContextMenu();
    298 }
    299 
    300 // static
    301 void BackgroundModeManager::LaunchBackgroundApplication(
    302     Profile* profile,
    303     const Extension* extension) {
    304   OpenApplication(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB));
    305 }
    306 
    307 bool BackgroundModeManager::IsBackgroundModeActive() {
    308   return in_background_mode_;
    309 }
    310 
    311 int BackgroundModeManager::NumberOfBackgroundModeData() {
    312   return background_mode_data_.size();
    313 }
    314 
    315 ///////////////////////////////////////////////////////////////////////////////
    316 //  BackgroundModeManager, content::NotificationObserver overrides
    317 void BackgroundModeManager::Observe(
    318     int type,
    319     const content::NotificationSource& source,
    320     const content::NotificationDetails& details) {
    321   switch (type) {
    322     case chrome::NOTIFICATION_EXTENSIONS_READY:
    323       // Extensions are loaded, so we don't need to manually keep the browser
    324       // process alive any more when running in no-startup-window mode.
    325       DecrementKeepAliveCountForStartup();
    326       break;
    327 
    328     case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
    329         Extension* extension = content::Details<Extension>(details).ptr();
    330         Profile* profile = content::Source<Profile>(source).ptr();
    331         if (BackgroundApplicationListModel::IsBackgroundApp(
    332                 *extension, profile)) {
    333           // Extensions loaded after the ExtensionsService is ready should be
    334           // treated as new installs.
    335           if (extensions::ExtensionSystem::Get(profile)->extension_service()->
    336                   is_ready()) {
    337             bool is_being_reloaded = false;
    338             CheckReloadStatus(extension, &is_being_reloaded);
    339             // No need to show the notification if we showed to the user
    340             // previously for this app.
    341             if (!is_being_reloaded)
    342               OnBackgroundAppInstalled(extension);
    343           }
    344         }
    345       }
    346       break;
    347     case chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED: {
    348         UpdatedExtensionPermissionsInfo* info =
    349             content::Details<UpdatedExtensionPermissionsInfo>(details).ptr();
    350         if (info->permissions->HasAPIPermission(
    351                 extensions::APIPermission::kBackground) &&
    352             info->reason == UpdatedExtensionPermissionsInfo::ADDED) {
    353           // Turned on background permission, so treat this as a new install.
    354           OnBackgroundAppInstalled(info->extension);
    355         }
    356       }
    357       break;
    358     case chrome::NOTIFICATION_APP_TERMINATING:
    359       // Make sure we aren't still keeping the app alive (only happens if we
    360       // don't receive an EXTENSIONS_READY notification for some reason).
    361       DecrementKeepAliveCountForStartup();
    362       // Performing an explicit shutdown, so exit background mode (does nothing
    363       // if we aren't in background mode currently).
    364       EndBackgroundMode();
    365       // Shutting down, so don't listen for any more notifications so we don't
    366       // try to re-enter/exit background mode again.
    367       registrar_.RemoveAll();
    368       for (BackgroundModeInfoMap::iterator it =
    369                background_mode_data_.begin();
    370            it != background_mode_data_.end();
    371            ++it) {
    372         it->second->applications_->RemoveObserver(this);
    373       }
    374       break;
    375     default:
    376       NOTREACHED();
    377       break;
    378   }
    379 }
    380 
    381 void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
    382   if (IsBackgroundModePrefEnabled())
    383     EnableBackgroundMode();
    384   else
    385     DisableBackgroundMode();
    386 }
    387 
    388 ///////////////////////////////////////////////////////////////////////////////
    389 //  BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
    390 void BackgroundModeManager::OnApplicationDataChanged(
    391     const Extension* extension, Profile* profile) {
    392   UpdateStatusTrayIconContextMenu();
    393 }
    394 
    395 void BackgroundModeManager::OnApplicationListChanged(Profile* profile) {
    396   if (!IsBackgroundModePrefEnabled())
    397     return;
    398 
    399   // Update the profile cache with the fact whether background apps are running
    400   // for this profile.
    401   size_t profile_index = profile_cache_->GetIndexOfProfileWithPath(
    402       profile->GetPath());
    403   if (profile_index != std::string::npos) {
    404     profile_cache_->SetBackgroundStatusOfProfileAtIndex(
    405         profile_index, GetBackgroundAppCountForProfile(profile) != 0);
    406   }
    407 
    408   if (!ShouldBeInBackgroundMode()) {
    409     // We've uninstalled our last background app, make sure we exit background
    410     // mode and no longer launch on startup.
    411     EnableLaunchOnStartup(false);
    412     EndBackgroundMode();
    413   } else {
    414     // We have at least one background app running - make sure we're in
    415     // background mode.
    416     if (!in_background_mode_) {
    417       // We're entering background mode - make sure we have launch-on-startup
    418       // enabled. On Mac, the platform-specific code tracks whether the user
    419       // has deleted a login item in the past, and if so, no login item will
    420       // be created (to avoid overriding the specific user action).
    421       EnableLaunchOnStartup(true);
    422 
    423       StartBackgroundMode();
    424     }
    425     // List of applications changed so update the UI.
    426     UpdateStatusTrayIconContextMenu();
    427   }
    428 }
    429 
    430 ///////////////////////////////////////////////////////////////////////////////
    431 //  BackgroundModeManager, ProfileInfoCacheObserver overrides
    432 void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) {
    433   ProfileInfoCache& cache =
    434       g_browser_process->profile_manager()->GetProfileInfoCache();
    435   base::string16 profile_name = cache.GetNameOfProfileAtIndex(
    436       cache.GetIndexOfProfileWithPath(profile_path));
    437   // At this point, the profile should be registered with the background mode
    438   // manager, but when it's actually added to the cache is when its name is
    439   // set so we need up to update that with the background_mode_data.
    440   for (BackgroundModeInfoMap::const_iterator it =
    441        background_mode_data_.begin();
    442        it != background_mode_data_.end();
    443        ++it) {
    444     if (it->first->GetPath() == profile_path) {
    445       it->second->SetName(profile_name);
    446       UpdateStatusTrayIconContextMenu();
    447       return;
    448     }
    449   }
    450 }
    451 
    452 void BackgroundModeManager::OnProfileWillBeRemoved(
    453     const base::FilePath& profile_path) {
    454   ProfileInfoCache& cache =
    455       g_browser_process->profile_manager()->GetProfileInfoCache();
    456   base::string16 profile_name = cache.GetNameOfProfileAtIndex(
    457       cache.GetIndexOfProfileWithPath(profile_path));
    458   // Remove the profile from our map of profiles.
    459   BackgroundModeInfoMap::iterator it =
    460       GetBackgroundModeIterator(profile_name);
    461   // If a profile isn't running a background app, it may not be in the map.
    462   if (it != background_mode_data_.end()) {
    463     it->second->applications_->RemoveObserver(this);
    464     background_mode_data_.erase(it);
    465     // If there are no background mode profiles any longer, then turn off
    466     // background mode.
    467     if (!ShouldBeInBackgroundMode()) {
    468       EnableLaunchOnStartup(false);
    469       EndBackgroundMode();
    470     }
    471     UpdateStatusTrayIconContextMenu();
    472   }
    473 }
    474 
    475 void BackgroundModeManager::OnProfileNameChanged(
    476     const base::FilePath& profile_path,
    477     const base::string16& old_profile_name) {
    478   ProfileInfoCache& cache =
    479       g_browser_process->profile_manager()->GetProfileInfoCache();
    480   base::string16 new_profile_name = cache.GetNameOfProfileAtIndex(
    481       cache.GetIndexOfProfileWithPath(profile_path));
    482   BackgroundModeInfoMap::const_iterator it =
    483       GetBackgroundModeIterator(old_profile_name);
    484   // We check that the returned iterator is valid due to unittests, but really
    485   // this should only be called on profiles already known by the background
    486   // mode manager.
    487   if (it != background_mode_data_.end()) {
    488     it->second->SetName(new_profile_name);
    489     UpdateStatusTrayIconContextMenu();
    490   }
    491 }
    492 
    493 ///////////////////////////////////////////////////////////////////////////////
    494 //  BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
    495 void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) {
    496   // When a browser window is necessary, we use the first profile. The windows
    497   // opened for these commands are not profile-specific, so any profile would
    498   // work and the first is convenient.
    499   BackgroundModeData* bmd = background_mode_data_.begin()->second.get();
    500   switch (command_id) {
    501     case IDC_ABOUT:
    502       chrome::ShowAboutChrome(bmd->GetBrowserWindow());
    503       break;
    504     case IDC_TASK_MANAGER:
    505       chrome::OpenTaskManager(bmd->GetBrowserWindow());
    506       break;
    507     case IDC_EXIT:
    508       content::RecordAction(UserMetricsAction("Exit"));
    509       chrome::CloseAllBrowsers();
    510       break;
    511     case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: {
    512       // Background mode must already be enabled (as otherwise this menu would
    513       // not be visible).
    514       DCHECK(IsBackgroundModePrefEnabled());
    515       DCHECK(chrome::WillKeepAlive());
    516 
    517       // Set the background mode pref to "disabled" - the resulting notification
    518       // will result in a call to DisableBackgroundMode().
    519       PrefService* service = g_browser_process->local_state();
    520       DCHECK(service);
    521       service->SetBoolean(prefs::kBackgroundModeEnabled, false);
    522       break;
    523     }
    524     default:
    525       bmd->ExecuteCommand(command_id, event_flags);
    526       break;
    527   }
    528 }
    529 
    530 
    531 ///////////////////////////////////////////////////////////////////////////////
    532 //  BackgroundModeManager, private
    533 void BackgroundModeManager::DecrementKeepAliveCountForStartup() {
    534   if (keep_alive_for_startup_) {
    535     keep_alive_for_startup_ = false;
    536     // We call this via the message queue to make sure we don't try to end
    537     // keep-alive (which can shutdown Chrome) before the message loop has
    538     // started.
    539     base::MessageLoop::current()->PostTask(
    540         FROM_HERE, base::Bind(&chrome::DecrementKeepAliveCount));
    541   }
    542 }
    543 
    544 void BackgroundModeManager::StartBackgroundMode() {
    545   DCHECK(ShouldBeInBackgroundMode());
    546   // Don't bother putting ourselves in background mode if we're already there
    547   // or if background mode is disabled.
    548   if (in_background_mode_)
    549     return;
    550 
    551   // Mark ourselves as running in background mode.
    552   in_background_mode_ = true;
    553 
    554   UpdateKeepAliveAndTrayIcon();
    555 
    556   content::NotificationService::current()->Notify(
    557       chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
    558       content::Source<BackgroundModeManager>(this),
    559       content::Details<bool>(&in_background_mode_));
    560 }
    561 
    562 void BackgroundModeManager::EndBackgroundMode() {
    563   if (!in_background_mode_)
    564     return;
    565   in_background_mode_ = false;
    566 
    567   UpdateKeepAliveAndTrayIcon();
    568 
    569   content::NotificationService::current()->Notify(
    570       chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
    571       content::Source<BackgroundModeManager>(this),
    572       content::Details<bool>(&in_background_mode_));
    573 }
    574 
    575 void BackgroundModeManager::EnableBackgroundMode() {
    576   DCHECK(IsBackgroundModePrefEnabled());
    577   // If background mode should be enabled, but isn't, turn it on.
    578   if (!in_background_mode_ && ShouldBeInBackgroundMode()) {
    579     StartBackgroundMode();
    580     EnableLaunchOnStartup(true);
    581   }
    582 }
    583 
    584 void BackgroundModeManager::DisableBackgroundMode() {
    585   DCHECK(!IsBackgroundModePrefEnabled());
    586   // If background mode is currently enabled, turn it off.
    587   if (in_background_mode_) {
    588     EndBackgroundMode();
    589     EnableLaunchOnStartup(false);
    590   }
    591 }
    592 
    593 void BackgroundModeManager::SuspendBackgroundMode() {
    594   background_mode_suspended_ = true;
    595   UpdateKeepAliveAndTrayIcon();
    596 }
    597 
    598 void BackgroundModeManager::ResumeBackgroundMode() {
    599   background_mode_suspended_ = false;
    600   UpdateKeepAliveAndTrayIcon();
    601 }
    602 
    603 void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
    604   if (in_background_mode_ && !background_mode_suspended_) {
    605     if (!keeping_alive_) {
    606       keeping_alive_ = true;
    607       chrome::IncrementKeepAliveCount();
    608     }
    609     CreateStatusTrayIcon();
    610     return;
    611   }
    612 
    613   RemoveStatusTrayIcon();
    614   if (keeping_alive_) {
    615     keeping_alive_ = false;
    616     chrome::DecrementKeepAliveCount();
    617   }
    618 }
    619 
    620 void BackgroundModeManager::OnBrowserAdded(Browser* browser) {
    621   ResumeBackgroundMode();
    622 }
    623 
    624 int BackgroundModeManager::GetBackgroundAppCount() const {
    625   int count = 0;
    626   // Walk the BackgroundModeData for all profiles and count the number of apps.
    627   for (BackgroundModeInfoMap::const_iterator it =
    628        background_mode_data_.begin();
    629        it != background_mode_data_.end();
    630        ++it) {
    631     count += it->second->GetBackgroundAppCount();
    632   }
    633   DCHECK(count >= 0);
    634   return count;
    635 }
    636 
    637 int BackgroundModeManager::GetBackgroundAppCountForProfile(
    638     Profile* const profile) const {
    639   BackgroundModeData* bmd = GetBackgroundModeData(profile);
    640   return bmd->GetBackgroundAppCount();
    641 }
    642 
    643 bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
    644   return IsBackgroundModePrefEnabled() &&
    645       (GetBackgroundAppCount() > 0 || keep_alive_for_test_);
    646 }
    647 
    648 void BackgroundModeManager::OnBackgroundAppInstalled(
    649     const Extension* extension) {
    650   // Background mode is disabled - don't do anything.
    651   if (!IsBackgroundModePrefEnabled())
    652     return;
    653 
    654   // Ensure we have a tray icon (needed so we can display the app-installed
    655   // notification below).
    656   EnableBackgroundMode();
    657   ResumeBackgroundMode();
    658 
    659   // Notify the user that a background app has been installed.
    660   if (extension) {  // NULL when called by unit tests.
    661     DisplayAppInstalledNotification(extension);
    662   }
    663 }
    664 
    665 void BackgroundModeManager::CheckReloadStatus(
    666     const Extension* extension,
    667     bool* is_being_reloaded) {
    668     // Walk the BackgroundModeData for all profiles to see if one of their
    669     // extensions is being reloaded.
    670     for (BackgroundModeInfoMap::const_iterator it =
    671              background_mode_data_.begin();
    672          it != background_mode_data_.end();
    673          ++it) {
    674       Profile* profile = it->first;
    675       // If the extension is being reloaded, no need to show a notification.
    676       if (profile->GetExtensionService()->IsBeingReloaded(extension->id()))
    677         *is_being_reloaded = true;
    678     }
    679 }
    680 
    681 void BackgroundModeManager::CreateStatusTrayIcon() {
    682   // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
    683   // Chrome and Mac can use the dock icon instead.
    684 
    685   // Since there are multiple profiles which share the status tray, we now
    686   // use the browser process to keep track of it.
    687 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
    688   if (!status_tray_)
    689     status_tray_ = g_browser_process->status_tray();
    690 #endif
    691 
    692   // If the platform doesn't support status icons, or we've already created
    693   // our status icon, just return.
    694   if (!status_tray_ || status_icon_)
    695     return;
    696 
    697   // TODO(rlp): Status tray icon should have submenus for each profile.
    698   gfx::ImageSkia* image_skia = ui::ResourceBundle::GetSharedInstance().
    699       GetImageSkiaNamed(IDR_STATUS_TRAY_ICON);
    700 
    701   status_icon_ = status_tray_->CreateStatusIcon(
    702       StatusTray::BACKGROUND_MODE_ICON,
    703       *image_skia,
    704       l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
    705   if (!status_icon_)
    706     return;
    707   UpdateStatusTrayIconContextMenu();
    708 }
    709 
    710 void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
    711   // Ensure we have a tray icon if appropriate.
    712   UpdateKeepAliveAndTrayIcon();
    713 
    714   // If we don't have a status icon or one could not be created succesfully,
    715   // then no need to continue the update.
    716   if (!status_icon_)
    717     return;
    718 
    719   // We should only get here if we have a profile loaded, or if we're running
    720   // in test mode.
    721   if (background_mode_data_.empty()) {
    722     DCHECK(keep_alive_for_test_);
    723     return;
    724   }
    725 
    726   // We are building a new menu. Reset the Command IDs.
    727   command_id_extension_vector_.clear();
    728 
    729   // Clear the submenus too since we will be creating new ones.
    730   submenus.clear();
    731 
    732   // TODO(rlp): Add current profile color or indicator.
    733   // Create a context menu item for Chrome.
    734   scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
    735   // Add About item
    736   menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
    737   menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
    738   menu->AddSeparator(ui::NORMAL_SEPARATOR);
    739 
    740   if (profile_cache_->GetNumberOfProfiles() > 1) {
    741     std::vector<BackgroundModeData*> bmd_vector;
    742     for (BackgroundModeInfoMap::iterator it =
    743          background_mode_data_.begin();
    744          it != background_mode_data_.end();
    745          ++it) {
    746        bmd_vector.push_back(it->second.get());
    747     }
    748     std::sort(bmd_vector.begin(), bmd_vector.end(),
    749               &BackgroundModeData::BackgroundModeDataCompare);
    750     int profiles_with_apps = 0;
    751     for (std::vector<BackgroundModeData*>::const_iterator bmd_it =
    752          bmd_vector.begin();
    753          bmd_it != bmd_vector.end();
    754          ++bmd_it) {
    755       BackgroundModeData* bmd = *bmd_it;
    756       // We should only display the profile in the status icon if it has at
    757       // least one background app.
    758       if (bmd->GetBackgroundAppCount() > 0) {
    759         StatusIconMenuModel* submenu = new StatusIconMenuModel(bmd);
    760         // The submenu constructor caller owns the lifetime of the submenu.
    761         // The containing menu does not handle the lifetime.
    762         submenus.push_back(submenu);
    763         bmd->BuildProfileMenu(submenu, menu.get());
    764         profiles_with_apps++;
    765       }
    766     }
    767     // We should only be displaying the status tray icon if there is at least
    768     // one profile with a background app.
    769     DCHECK_GT(profiles_with_apps, 0);
    770   } else {
    771     // We should only have one profile in the cache if we are not
    772     // using multi-profiles. If keep_alive_for_test_ is set, then we may not
    773     // have any profiles in the cache.
    774     DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) ||
    775            keep_alive_for_test_);
    776     background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), NULL);
    777   }
    778 
    779   menu->AddSeparator(ui::NORMAL_SEPARATOR);
    780   menu->AddCheckItemWithStringId(
    781       IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
    782       IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
    783   menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
    784                             true);
    785 
    786   PrefService* service = g_browser_process->local_state();
    787   DCHECK(service);
    788   bool enabled =
    789       service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled);
    790   menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
    791                             enabled);
    792 
    793   menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
    794 
    795   context_menu_ = menu.get();
    796   status_icon_->SetContextMenu(menu.Pass());
    797 }
    798 
    799 void BackgroundModeManager::RemoveStatusTrayIcon() {
    800   if (status_icon_)
    801     status_tray_->RemoveStatusIcon(status_icon_);
    802   status_icon_ = NULL;
    803   context_menu_ = NULL;
    804 }
    805 
    806 BackgroundModeManager::BackgroundModeData*
    807 BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const {
    808   DCHECK(background_mode_data_.find(profile) != background_mode_data_.end());
    809   return background_mode_data_.find(profile)->second.get();
    810 }
    811 
    812 BackgroundModeManager::BackgroundModeInfoMap::iterator
    813 BackgroundModeManager::GetBackgroundModeIterator(
    814     const base::string16& profile_name) {
    815   BackgroundModeInfoMap::iterator profile_it =
    816       background_mode_data_.end();
    817   for (BackgroundModeInfoMap::iterator it =
    818        background_mode_data_.begin();
    819        it != background_mode_data_.end();
    820        ++it) {
    821     if (it->second->name() == profile_name) {
    822       profile_it = it;
    823     }
    824   }
    825   return profile_it;
    826 }
    827 
    828 bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
    829   PrefService* service = g_browser_process->local_state();
    830   DCHECK(service);
    831   return service->GetBoolean(prefs::kBackgroundModeEnabled);
    832 }
    833