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