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