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/extensions/extension_system.h"
     24 #include "chrome/browser/lifetime/application_lifetime.h"
     25 #include "chrome/browser/profiles/profile.h"
     26 #include "chrome/browser/profiles/profile_info_cache.h"
     27 #include "chrome/browser/profiles/profile_manager.h"
     28 #include "chrome/browser/status_icons/status_icon.h"
     29 #include "chrome/browser/status_icons/status_tray.h"
     30 #include "chrome/browser/ui/browser.h"
     31 #include "chrome/browser/ui/browser_commands.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/common/chrome_constants.h"
     38 #include "chrome/common/chrome_switches.h"
     39 #include "chrome/common/extensions/extension_constants.h"
     40 #include "chrome/common/extensions/manifest_url_handler.h"
     41 #include "chrome/common/pref_names.h"
     42 #include "content/public/browser/notification_service.h"
     43 #include "content/public/browser/user_metrics.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 content::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, 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::StartKeepAlive();
    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, chrome::NOTIFICATION_EXTENSION_LOADED,
    280                  content::Source<Profile>(profile));
    281   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
    282                  content::Source<Profile>(profile));
    283 
    284 
    285   // Check for the presence of background apps after all extensions have been
    286   // loaded, to handle the case where an extension has been manually removed
    287   // while Chrome was not running.
    288   registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
    289                  content::Source<Profile>(profile));
    290 
    291   bmd->applications_->AddObserver(this);
    292 
    293   // If we're adding a new profile and running in multi-profile mode, this new
    294   // profile should be added to the status icon if one currently exists.
    295   if (in_background_mode_ && status_icon_)
    296     UpdateStatusTrayIconContextMenu();
    297 }
    298 
    299 // static
    300 void BackgroundModeManager::LaunchBackgroundApplication(
    301     Profile* profile,
    302     const Extension* extension) {
    303   OpenApplication(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB));
    304 }
    305 
    306 bool BackgroundModeManager::IsBackgroundModeActive() {
    307   return in_background_mode_;
    308 }
    309 
    310 int BackgroundModeManager::NumberOfBackgroundModeData() {
    311   return background_mode_data_.size();
    312 }
    313 
    314 ///////////////////////////////////////////////////////////////////////////////
    315 //  BackgroundModeManager, content::NotificationObserver overrides
    316 void BackgroundModeManager::Observe(
    317     int type,
    318     const content::NotificationSource& source,
    319     const content::NotificationDetails& details) {
    320   switch (type) {
    321     case chrome::NOTIFICATION_EXTENSIONS_READY:
    322       // Extensions are loaded, so we don't need to manually keep the browser
    323       // process alive any more when running in no-startup-window mode.
    324       EndKeepAliveForStartup();
    325       break;
    326 
    327     case chrome::NOTIFICATION_EXTENSION_LOADED: {
    328         Extension* extension = content::Details<Extension>(details).ptr();
    329         Profile* profile = content::Source<Profile>(source).ptr();
    330         if (BackgroundApplicationListModel::IsBackgroundApp(
    331                 *extension, profile)) {
    332           // Extensions loaded after the ExtensionsService is ready should be
    333           // treated as new installs.
    334           if (extensions::ExtensionSystem::Get(profile)->extension_service()->
    335                   is_ready()) {
    336             bool is_being_reloaded = false;
    337             CheckReloadStatus(extension, &is_being_reloaded);
    338             // No need to show the notification if we showed to the user
    339             // previously for this app.
    340             if (!is_being_reloaded)
    341               OnBackgroundAppInstalled(extension);
    342           }
    343         }
    344       }
    345       break;
    346     case chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED: {
    347         UpdatedExtensionPermissionsInfo* info =
    348             content::Details<UpdatedExtensionPermissionsInfo>(details).ptr();
    349         if (info->permissions->HasAPIPermission(
    350                 extensions::APIPermission::kBackground) &&
    351             info->reason == UpdatedExtensionPermissionsInfo::ADDED) {
    352           // Turned on background permission, so treat this as a new install.
    353           OnBackgroundAppInstalled(info->extension);
    354         }
    355       }
    356       break;
    357     case chrome::NOTIFICATION_APP_TERMINATING:
    358       // Make sure we aren't still keeping the app alive (only happens if we
    359       // don't receive an EXTENSIONS_READY notification for some reason).
    360       EndKeepAliveForStartup();
    361       // Performing an explicit shutdown, so exit background mode (does nothing
    362       // if we aren't in background mode currently).
    363       EndBackgroundMode();
    364       // Shutting down, so don't listen for any more notifications so we don't
    365       // try to re-enter/exit background mode again.
    366       registrar_.RemoveAll();
    367       for (BackgroundModeInfoMap::iterator it =
    368                background_mode_data_.begin();
    369            it != background_mode_data_.end();
    370            ++it) {
    371         it->second->applications_->RemoveObserver(this);
    372       }
    373       break;
    374     default:
    375       NOTREACHED();
    376       break;
    377   }
    378 }
    379 
    380 void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
    381   if (IsBackgroundModePrefEnabled())
    382     EnableBackgroundMode();
    383   else
    384     DisableBackgroundMode();
    385 }
    386 
    387 ///////////////////////////////////////////////////////////////////////////////
    388 //  BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
    389 void BackgroundModeManager::OnApplicationDataChanged(
    390     const Extension* extension, Profile* profile) {
    391   UpdateStatusTrayIconContextMenu();
    392 }
    393 
    394 void BackgroundModeManager::OnApplicationListChanged(Profile* profile) {
    395   if (!IsBackgroundModePrefEnabled())
    396     return;
    397 
    398   // Update the profile cache with the fact whether background apps are running
    399   // for this profile.
    400   size_t profile_index = profile_cache_->GetIndexOfProfileWithPath(
    401       profile->GetPath());
    402   if (profile_index != std::string::npos) {
    403     profile_cache_->SetBackgroundStatusOfProfileAtIndex(
    404         profile_index, GetBackgroundAppCountForProfile(profile) != 0);
    405   }
    406 
    407   if (!ShouldBeInBackgroundMode()) {
    408     // We've uninstalled our last background app, make sure we exit background
    409     // mode and no longer launch on startup.
    410     EnableLaunchOnStartup(false);
    411     EndBackgroundMode();
    412   } else {
    413     // We have at least one background app running - make sure we're in
    414     // background mode.
    415     if (!in_background_mode_) {
    416       // We're entering background mode - make sure we have launch-on-startup
    417       // enabled. On Mac, the platform-specific code tracks whether the user
    418       // has deleted a login item in the past, and if so, no login item will
    419       // be created (to avoid overriding the specific user action).
    420       EnableLaunchOnStartup(true);
    421 
    422       StartBackgroundMode();
    423     }
    424     // List of applications changed so update the UI.
    425     UpdateStatusTrayIconContextMenu();
    426   }
    427 }
    428 
    429 ///////////////////////////////////////////////////////////////////////////////
    430 //  BackgroundModeManager, ProfileInfoCacheObserver overrides
    431 void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) {
    432   ProfileInfoCache& cache =
    433       g_browser_process->profile_manager()->GetProfileInfoCache();
    434   base::string16 profile_name = cache.GetNameOfProfileAtIndex(
    435       cache.GetIndexOfProfileWithPath(profile_path));
    436   // At this point, the profile should be registered with the background mode
    437   // manager, but when it's actually added to the cache is when its name is
    438   // set so we need up to update that with the background_mode_data.
    439   for (BackgroundModeInfoMap::const_iterator it =
    440        background_mode_data_.begin();
    441        it != background_mode_data_.end();
    442        ++it) {
    443     if (it->first->GetPath() == profile_path) {
    444       it->second->SetName(profile_name);
    445       UpdateStatusTrayIconContextMenu();
    446       return;
    447     }
    448   }
    449 }
    450 
    451 void BackgroundModeManager::OnProfileWillBeRemoved(
    452     const base::FilePath& profile_path) {
    453   ProfileInfoCache& cache =
    454       g_browser_process->profile_manager()->GetProfileInfoCache();
    455   base::string16 profile_name = cache.GetNameOfProfileAtIndex(
    456       cache.GetIndexOfProfileWithPath(profile_path));
    457   // Remove the profile from our map of profiles.
    458   BackgroundModeInfoMap::iterator it =
    459       GetBackgroundModeIterator(profile_name);
    460   // If a profile isn't running a background app, it may not be in the map.
    461   if (it != background_mode_data_.end()) {
    462     background_mode_data_.erase(it);
    463     UpdateStatusTrayIconContextMenu();
    464   }
    465 }
    466 
    467 void BackgroundModeManager::OnProfileNameChanged(
    468     const base::FilePath& profile_path,
    469     const base::string16& old_profile_name) {
    470   ProfileInfoCache& cache =
    471       g_browser_process->profile_manager()->GetProfileInfoCache();
    472   base::string16 new_profile_name = cache.GetNameOfProfileAtIndex(
    473       cache.GetIndexOfProfileWithPath(profile_path));
    474   BackgroundModeInfoMap::const_iterator it =
    475       GetBackgroundModeIterator(old_profile_name);
    476   // We check that the returned iterator is valid due to unittests, but really
    477   // this should only be called on profiles already known by the background
    478   // mode manager.
    479   if (it != background_mode_data_.end()) {
    480     it->second->SetName(new_profile_name);
    481     UpdateStatusTrayIconContextMenu();
    482   }
    483 }
    484 
    485 ///////////////////////////////////////////////////////////////////////////////
    486 //  BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
    487 void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) {
    488   // When a browser window is necessary, we use the first profile. The windows
    489   // opened for these commands are not profile-specific, so any profile would
    490   // work and the first is convenient.
    491   BackgroundModeData* bmd = background_mode_data_.begin()->second.get();
    492   switch (command_id) {
    493     case IDC_ABOUT:
    494       chrome::ShowAboutChrome(bmd->GetBrowserWindow());
    495       break;
    496     case IDC_TASK_MANAGER:
    497       chrome::OpenTaskManager(bmd->GetBrowserWindow());
    498       break;
    499     case IDC_EXIT:
    500       content::RecordAction(UserMetricsAction("Exit"));
    501       chrome::CloseAllBrowsers();
    502       break;
    503     case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: {
    504       // Background mode must already be enabled (as otherwise this menu would
    505       // not be visible).
    506       DCHECK(IsBackgroundModePrefEnabled());
    507       DCHECK(chrome::WillKeepAlive());
    508 
    509       // Set the background mode pref to "disabled" - the resulting notification
    510       // will result in a call to DisableBackgroundMode().
    511       PrefService* service = g_browser_process->local_state();
    512       DCHECK(service);
    513       service->SetBoolean(prefs::kBackgroundModeEnabled, false);
    514       break;
    515     }
    516     default:
    517       bmd->ExecuteCommand(command_id, event_flags);
    518       break;
    519   }
    520 }
    521 
    522 
    523 ///////////////////////////////////////////////////////////////////////////////
    524 //  BackgroundModeManager, private
    525 void BackgroundModeManager::EndKeepAliveForStartup() {
    526   if (keep_alive_for_startup_) {
    527     keep_alive_for_startup_ = false;
    528     // We call this via the message queue to make sure we don't try to end
    529     // keep-alive (which can shutdown Chrome) before the message loop has
    530     // started.
    531     base::MessageLoop::current()->PostTask(FROM_HERE,
    532                                            base::Bind(&chrome::EndKeepAlive));
    533   }
    534 }
    535 
    536 void BackgroundModeManager::StartBackgroundMode() {
    537   DCHECK(ShouldBeInBackgroundMode());
    538   // Don't bother putting ourselves in background mode if we're already there
    539   // or if background mode is disabled.
    540   if (in_background_mode_)
    541     return;
    542 
    543   // Mark ourselves as running in background mode.
    544   in_background_mode_ = true;
    545 
    546   UpdateKeepAliveAndTrayIcon();
    547 
    548   content::NotificationService::current()->Notify(
    549       chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
    550       content::Source<BackgroundModeManager>(this),
    551       content::Details<bool>(&in_background_mode_));
    552 }
    553 
    554 void BackgroundModeManager::EndBackgroundMode() {
    555   if (!in_background_mode_)
    556     return;
    557   in_background_mode_ = false;
    558 
    559   UpdateKeepAliveAndTrayIcon();
    560 
    561   content::NotificationService::current()->Notify(
    562       chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
    563       content::Source<BackgroundModeManager>(this),
    564       content::Details<bool>(&in_background_mode_));
    565 }
    566 
    567 void BackgroundModeManager::EnableBackgroundMode() {
    568   DCHECK(IsBackgroundModePrefEnabled());
    569   // If background mode should be enabled, but isn't, turn it on.
    570   if (!in_background_mode_ && ShouldBeInBackgroundMode()) {
    571     StartBackgroundMode();
    572     EnableLaunchOnStartup(true);
    573   }
    574 }
    575 
    576 void BackgroundModeManager::DisableBackgroundMode() {
    577   DCHECK(!IsBackgroundModePrefEnabled());
    578   // If background mode is currently enabled, turn it off.
    579   if (in_background_mode_) {
    580     EndBackgroundMode();
    581     EnableLaunchOnStartup(false);
    582   }
    583 }
    584 
    585 void BackgroundModeManager::SuspendBackgroundMode() {
    586   background_mode_suspended_ = true;
    587   UpdateKeepAliveAndTrayIcon();
    588 }
    589 
    590 void BackgroundModeManager::ResumeBackgroundMode() {
    591   background_mode_suspended_ = false;
    592   UpdateKeepAliveAndTrayIcon();
    593 }
    594 
    595 void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
    596   if (in_background_mode_ && !background_mode_suspended_) {
    597     if (!keeping_alive_) {
    598       keeping_alive_ = true;
    599       chrome::StartKeepAlive();
    600     }
    601     CreateStatusTrayIcon();
    602     return;
    603   }
    604 
    605   RemoveStatusTrayIcon();
    606   if (keeping_alive_) {
    607     keeping_alive_ = false;
    608     chrome::EndKeepAlive();
    609   }
    610 }
    611 
    612 void BackgroundModeManager::OnBrowserAdded(Browser* browser) {
    613   ResumeBackgroundMode();
    614 }
    615 
    616 int BackgroundModeManager::GetBackgroundAppCount() const {
    617   int count = 0;
    618   // Walk the BackgroundModeData for all profiles and count the number of apps.
    619   for (BackgroundModeInfoMap::const_iterator it =
    620        background_mode_data_.begin();
    621        it != background_mode_data_.end();
    622        ++it) {
    623     count += it->second->GetBackgroundAppCount();
    624   }
    625   DCHECK(count >= 0);
    626   return count;
    627 }
    628 
    629 int BackgroundModeManager::GetBackgroundAppCountForProfile(
    630     Profile* const profile) const {
    631   BackgroundModeData* bmd = GetBackgroundModeData(profile);
    632   return bmd->GetBackgroundAppCount();
    633 }
    634 
    635 bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
    636   return IsBackgroundModePrefEnabled() &&
    637       (GetBackgroundAppCount() > 0 || keep_alive_for_test_);
    638 }
    639 
    640 void BackgroundModeManager::OnBackgroundAppInstalled(
    641     const Extension* extension) {
    642   // Background mode is disabled - don't do anything.
    643   if (!IsBackgroundModePrefEnabled())
    644     return;
    645 
    646   // Ensure we have a tray icon (needed so we can display the app-installed
    647   // notification below).
    648   EnableBackgroundMode();
    649   ResumeBackgroundMode();
    650 
    651   // Notify the user that a background app has been installed.
    652   if (extension) {  // NULL when called by unit tests.
    653     DisplayAppInstalledNotification(extension);
    654   }
    655 }
    656 
    657 void BackgroundModeManager::CheckReloadStatus(
    658     const Extension* extension,
    659     bool* is_being_reloaded) {
    660     // Walk the BackgroundModeData for all profiles to see if one of their
    661     // extensions is being reloaded.
    662     for (BackgroundModeInfoMap::const_iterator it =
    663              background_mode_data_.begin();
    664          it != background_mode_data_.end();
    665          ++it) {
    666       Profile* profile = it->first;
    667       // If the extension is being reloaded, no need to show a notification.
    668       if (profile->GetExtensionService()->IsBeingReloaded(extension->id()))
    669         *is_being_reloaded = true;
    670     }
    671 }
    672 
    673 void BackgroundModeManager::CreateStatusTrayIcon() {
    674   // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
    675   // Chrome and Mac can use the dock icon instead.
    676 
    677   // Since there are multiple profiles which share the status tray, we now
    678   // use the browser process to keep track of it.
    679 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
    680   if (!status_tray_)
    681     status_tray_ = g_browser_process->status_tray();
    682 #endif
    683 
    684   // If the platform doesn't support status icons, or we've already created
    685   // our status icon, just return.
    686   if (!status_tray_ || status_icon_)
    687     return;
    688 
    689   // TODO(rlp): Status tray icon should have submenus for each profile.
    690   gfx::ImageSkia* image_skia = ui::ResourceBundle::GetSharedInstance().
    691       GetImageSkiaNamed(IDR_STATUS_TRAY_ICON);
    692 
    693   status_icon_ = status_tray_->CreateStatusIcon(
    694       StatusTray::BACKGROUND_MODE_ICON,
    695       *image_skia,
    696       l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
    697   if (!status_icon_)
    698     return;
    699   UpdateStatusTrayIconContextMenu();
    700 }
    701 
    702 void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
    703   // Ensure we have a tray icon if appropriate.
    704   UpdateKeepAliveAndTrayIcon();
    705 
    706   // If we don't have a status icon or one could not be created succesfully,
    707   // then no need to continue the update.
    708   if (!status_icon_)
    709     return;
    710 
    711   // We should only get here if we have a profile loaded, or if we're running
    712   // in test mode.
    713   if (background_mode_data_.empty()) {
    714     DCHECK(keep_alive_for_test_);
    715     return;
    716   }
    717 
    718   // We are building a new menu. Reset the Command IDs.
    719   command_id_extension_vector_.clear();
    720 
    721   // Clear the submenus too since we will be creating new ones.
    722   submenus.clear();
    723 
    724   // TODO(rlp): Add current profile color or indicator.
    725   // Create a context menu item for Chrome.
    726   scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
    727   // Add About item
    728   menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
    729   menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
    730   menu->AddSeparator(ui::NORMAL_SEPARATOR);
    731 
    732   if (profile_cache_->GetNumberOfProfiles() > 1) {
    733     std::vector<BackgroundModeData*> bmd_vector;
    734     for (BackgroundModeInfoMap::iterator it =
    735          background_mode_data_.begin();
    736          it != background_mode_data_.end();
    737          ++it) {
    738        bmd_vector.push_back(it->second.get());
    739     }
    740     std::sort(bmd_vector.begin(), bmd_vector.end(),
    741               &BackgroundModeData::BackgroundModeDataCompare);
    742     int profiles_with_apps = 0;
    743     for (std::vector<BackgroundModeData*>::const_iterator bmd_it =
    744          bmd_vector.begin();
    745          bmd_it != bmd_vector.end();
    746          ++bmd_it) {
    747       BackgroundModeData* bmd = *bmd_it;
    748       // We should only display the profile in the status icon if it has at
    749       // least one background app.
    750       if (bmd->GetBackgroundAppCount() > 0) {
    751         StatusIconMenuModel* submenu = new StatusIconMenuModel(bmd);
    752         // The submenu constructor caller owns the lifetime of the submenu.
    753         // The containing menu does not handle the lifetime.
    754         submenus.push_back(submenu);
    755         bmd->BuildProfileMenu(submenu, menu.get());
    756         profiles_with_apps++;
    757       }
    758     }
    759     // We should only be displaying the status tray icon if there is at least
    760     // one profile with a background app.
    761     DCHECK_GT(profiles_with_apps, 0);
    762   } else {
    763     // We should only have one profile in the cache if we are not
    764     // using multi-profiles. If keep_alive_for_test_ is set, then we may not
    765     // have any profiles in the cache.
    766     DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) ||
    767            keep_alive_for_test_);
    768     background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), NULL);
    769   }
    770 
    771   menu->AddSeparator(ui::NORMAL_SEPARATOR);
    772   menu->AddCheckItemWithStringId(
    773       IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
    774       IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
    775   menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
    776                             true);
    777 
    778   PrefService* service = g_browser_process->local_state();
    779   DCHECK(service);
    780   bool enabled =
    781       service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled);
    782   menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
    783                             enabled);
    784 
    785   menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
    786 
    787   context_menu_ = menu.get();
    788   status_icon_->SetContextMenu(menu.Pass());
    789 }
    790 
    791 void BackgroundModeManager::RemoveStatusTrayIcon() {
    792   if (status_icon_)
    793     status_tray_->RemoveStatusIcon(status_icon_);
    794   status_icon_ = NULL;
    795   context_menu_ = NULL;
    796 }
    797 
    798 BackgroundModeManager::BackgroundModeData*
    799 BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const {
    800   DCHECK(background_mode_data_.find(profile) != background_mode_data_.end());
    801   return background_mode_data_.find(profile)->second.get();
    802 }
    803 
    804 BackgroundModeManager::BackgroundModeInfoMap::iterator
    805 BackgroundModeManager::GetBackgroundModeIterator(
    806     const base::string16& profile_name) {
    807   BackgroundModeInfoMap::iterator profile_it =
    808       background_mode_data_.end();
    809   for (BackgroundModeInfoMap::iterator it =
    810        background_mode_data_.begin();
    811        it != background_mode_data_.end();
    812        ++it) {
    813     if (it->second->name() == profile_name) {
    814       profile_it = it;
    815     }
    816   }
    817   return profile_it;
    818 }
    819 
    820 bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
    821   PrefService* service = g_browser_process->local_state();
    822   DCHECK(service);
    823   return service->GetBoolean(prefs::kBackgroundModeEnabled);
    824 }
    825