background_mode_manager.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <algorithm>
6#include <string>
7#include <vector>
8
9#include "base/base_paths.h"
10#include "base/bind.h"
11#include "base/command_line.h"
12#include "base/logging.h"
13#include "base/prefs/pref_registry_simple.h"
14#include "base/prefs/pref_service.h"
15#include "base/strings/utf_string_conversions.h"
16#include "chrome/app/chrome_command_ids.h"
17#include "chrome/browser/background/background_application_list_model.h"
18#include "chrome/browser/background/background_mode_manager.h"
19#include "chrome/browser/browser_process.h"
20#include "chrome/browser/browser_shutdown.h"
21#include "chrome/browser/chrome_notification_types.h"
22#include "chrome/browser/extensions/extension_service.h"
23#include "chrome/browser/lifetime/application_lifetime.h"
24#include "chrome/browser/profiles/profile.h"
25#include "chrome/browser/profiles/profile_info_cache.h"
26#include "chrome/browser/profiles/profile_manager.h"
27#include "chrome/browser/status_icons/status_icon.h"
28#include "chrome/browser/status_icons/status_tray.h"
29#include "chrome/browser/ui/browser.h"
30#include "chrome/browser/ui/browser_commands.h"
31#include "chrome/browser/ui/browser_finder.h"
32#include "chrome/browser/ui/browser_list.h"
33#include "chrome/browser/ui/chrome_pages.h"
34#include "chrome/browser/ui/extensions/application_launch.h"
35#include "chrome/browser/ui/host_desktop.h"
36#include "chrome/common/chrome_constants.h"
37#include "chrome/common/chrome_switches.h"
38#include "chrome/common/extensions/extension_constants.h"
39#include "chrome/common/extensions/manifest_url_handler.h"
40#include "chrome/common/pref_names.h"
41#include "chrome/grit/chromium_strings.h"
42#include "chrome/grit/generated_resources.h"
43#include "content/public/browser/notification_service.h"
44#include "content/public/browser/user_metrics.h"
45#include "extensions/browser/extension_system.h"
46#include "extensions/common/extension.h"
47#include "extensions/common/permissions/permission_set.h"
48#include "grit/chrome_unscaled_resources.h"
49#include "ui/base/l10n/l10n_util.h"
50#include "ui/base/resource/resource_bundle.h"
51
52using base::UserMetricsAction;
53using extensions::Extension;
54using extensions::UpdatedExtensionPermissionsInfo;
55
56namespace {
57const int kInvalidExtensionIndex = -1;
58}
59
60BackgroundModeManager::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
68BackgroundModeManager::BackgroundModeData::~BackgroundModeData() {
69}
70
71///////////////////////////////////////////////////////////////////////////////
72//  BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
73void 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
92Browser* 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
100int BackgroundModeManager::BackgroundModeData::GetBackgroundAppCount() const {
101  return applications_->size();
102}
103
104void BackgroundModeManager::BackgroundModeData::BuildProfileMenu(
105    StatusIconMenuModel* menu,
106    StatusIconMenuModel* containing_menu) {
107  int position = 0;
108  // When there are no background applications, we want to display
109  // just a label stating that none are running.
110  if (applications_->size() < 1) {
111    menu->AddItemWithStringId(IDC_MinimumLabelValue,
112                              IDS_BACKGROUND_APP_NOT_INSTALLED);
113    menu->SetCommandIdEnabled(IDC_MinimumLabelValue, false);
114  } else {
115    for (extensions::ExtensionList::const_iterator cursor =
116             applications_->begin();
117         cursor != applications_->end();
118         ++cursor, ++position) {
119      const gfx::ImageSkia* icon = applications_->GetIcon(cursor->get());
120      DCHECK(position == applications_->GetPosition(cursor->get()));
121      const std::string& name = (*cursor)->name();
122      int command_id = command_id_extension_vector_->size();
123      // Check that the command ID is within the dynamic range.
124      DCHECK(command_id < IDC_MinimumLabelValue);
125      command_id_extension_vector_->push_back(position);
126      menu->AddItem(command_id, base::UTF8ToUTF16(name));
127      if (icon)
128        menu->SetIcon(menu->GetItemCount() - 1, gfx::Image(*icon));
129
130      // Component extensions with background that do not have an options page
131      // will cause this menu item to go to the extensions page with an
132      // absent component extension.
133      //
134      // Ideally, we would remove this item, but this conflicts with the user
135      // model where this menu shows the extensions with background.
136      //
137      // The compromise is to disable the item, avoiding the non-actionable
138      // navigate to the extensions page and preserving the user model.
139      if ((*cursor)->location() == extensions::Manifest::COMPONENT) {
140        GURL options_page = extensions::ManifestURL::GetOptionsPage(*cursor);
141        if (!options_page.is_valid())
142          menu->SetCommandIdEnabled(command_id, false);
143      }
144    }
145  }
146  if (containing_menu) {
147    int menu_command_id = command_id_extension_vector_->size();
148    // Check that the command ID is within the dynamic range.
149    DCHECK(menu_command_id < IDC_MinimumLabelValue);
150    command_id_extension_vector_->push_back(kInvalidExtensionIndex);
151    containing_menu->AddSubMenu(menu_command_id, name_, menu);
152  }
153}
154
155void BackgroundModeManager::BackgroundModeData::SetName(
156    const base::string16& new_profile_name) {
157  name_ = new_profile_name;
158}
159
160base::string16 BackgroundModeManager::BackgroundModeData::name() {
161  return name_;
162}
163
164std::set<const extensions::Extension*>
165BackgroundModeManager::BackgroundModeData::GetNewBackgroundApps() {
166  std::set<const extensions::Extension*> new_apps;
167
168  // Copy all current extensions into our list of |current_extensions_|.
169  for (extensions::ExtensionList::const_iterator it = applications_->begin();
170       it != applications_->end(); ++it) {
171    const extensions::ExtensionId& id = (*it)->id();
172    if (current_extensions_.count(id) == 0) {
173      // Not found in our set yet - add it and maybe return as a previously
174      // unseen extension.
175      current_extensions_.insert(id);
176      // If this application has been newly loaded after the initial startup,
177      // notify the user.
178      if (applications_->is_ready()) {
179        const extensions::Extension* extension = (*it).get();
180        new_apps.insert(extension);
181      }
182    }
183  }
184  return new_apps;
185}
186
187// static
188bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
189    const BackgroundModeData* bmd1,
190    const BackgroundModeData* bmd2) {
191  return bmd1->name_ < bmd2->name_;
192}
193
194
195///////////////////////////////////////////////////////////////////////////////
196//  BackgroundModeManager, public
197BackgroundModeManager::BackgroundModeManager(
198    CommandLine* command_line,
199    ProfileInfoCache* profile_cache)
200    : profile_cache_(profile_cache),
201      status_tray_(NULL),
202      status_icon_(NULL),
203      context_menu_(NULL),
204      in_background_mode_(false),
205      keep_alive_for_startup_(false),
206      keep_alive_for_test_(false),
207      background_mode_suspended_(false),
208      keeping_alive_(false) {
209  // We should never start up if there is no browser process or if we are
210  // currently quitting.
211  CHECK(g_browser_process != NULL);
212  CHECK(!browser_shutdown::IsTryingToQuit());
213
214  // Add self as an observer for the profile info cache so we know when profiles
215  // are deleted and their names change.
216  profile_cache_->AddObserver(this);
217
218  // Listen for the background mode preference changing.
219  if (g_browser_process->local_state()) {  // Skip for unit tests
220    pref_registrar_.Init(g_browser_process->local_state());
221    pref_registrar_.Add(
222        prefs::kBackgroundModeEnabled,
223        base::Bind(&BackgroundModeManager::OnBackgroundModeEnabledPrefChanged,
224                   base::Unretained(this)));
225  }
226
227  // Keep the browser alive until extensions are done loading - this is needed
228  // by the --no-startup-window flag. We want to stay alive until we load
229  // extensions, at which point we should either run in background mode (if
230  // there are background apps) or exit if there are none.
231  if (command_line->HasSwitch(switches::kNoStartupWindow)) {
232    keep_alive_for_startup_ = true;
233    chrome::IncrementKeepAliveCount();
234  } else {
235    // Otherwise, start with background mode suspended in case we're launching
236    // in a mode that doesn't open a browser window. It will be resumed when the
237    // first browser window is opened.
238    SuspendBackgroundMode();
239  }
240
241  // If the -keep-alive-for-test flag is passed, then always keep chrome running
242  // in the background until the user explicitly terminates it.
243  if (command_line->HasSwitch(switches::kKeepAliveForTest))
244    keep_alive_for_test_ = true;
245
246  if (ShouldBeInBackgroundMode())
247    StartBackgroundMode();
248
249  // Listen for the application shutting down so we can decrement our KeepAlive
250  // count.
251  registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
252                 content::NotificationService::AllSources());
253  BrowserList::AddObserver(this);
254}
255
256BackgroundModeManager::~BackgroundModeManager() {
257  // Remove ourselves from the application observer list (only needed by unit
258  // tests since APP_TERMINATING is what does this in a real running system).
259  for (BackgroundModeInfoMap::iterator it =
260       background_mode_data_.begin();
261       it != background_mode_data_.end();
262       ++it) {
263    it->second->applications_->RemoveObserver(this);
264  }
265  BrowserList::RemoveObserver(this);
266
267  // We're going away, so exit background mode (does nothing if we aren't in
268  // background mode currently). This is primarily needed for unit tests,
269  // because in an actual running system we'd get an APP_TERMINATING
270  // notification before being destroyed.
271  EndBackgroundMode();
272}
273
274// static
275void BackgroundModeManager::RegisterPrefs(PrefRegistrySimple* registry) {
276#if defined(OS_MACOSX)
277  registry->RegisterBooleanPref(prefs::kUserRemovedLoginItem, false);
278  registry->RegisterBooleanPref(prefs::kChromeCreatedLoginItem, false);
279  registry->RegisterBooleanPref(prefs::kMigratedLoginItemPref, false);
280#endif
281  registry->RegisterBooleanPref(prefs::kBackgroundModeEnabled, true);
282}
283
284
285void BackgroundModeManager::RegisterProfile(Profile* profile) {
286  // We don't want to register multiple times for one profile.
287  DCHECK(background_mode_data_.find(profile) == background_mode_data_.end());
288  BackgroundModeInfo bmd(new BackgroundModeData(profile,
289                                                &command_id_extension_vector_));
290  background_mode_data_[profile] = bmd;
291
292  // Initially set the name for this background mode data.
293  size_t index = profile_cache_->GetIndexOfProfileWithPath(profile->GetPath());
294  base::string16 name = l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME);
295  if (index != std::string::npos)
296    name = profile_cache_->GetNameOfProfileAtIndex(index);
297  bmd->SetName(name);
298
299  // Check for the presence of background apps after all extensions have been
300  // loaded, to handle the case where an extension has been manually removed
301  // while Chrome was not running.
302  registrar_.Add(this,
303                 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
304                 content::Source<Profile>(profile));
305
306  bmd->applications_->AddObserver(this);
307
308  // If we're adding a new profile and running in multi-profile mode, this new
309  // profile should be added to the status icon if one currently exists.
310  if (in_background_mode_ && status_icon_)
311    UpdateStatusTrayIconContextMenu();
312}
313
314// static
315void BackgroundModeManager::LaunchBackgroundApplication(
316    Profile* profile,
317    const Extension* extension) {
318  OpenApplication(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB));
319}
320
321bool BackgroundModeManager::IsBackgroundModeActive() {
322  return in_background_mode_;
323}
324
325int BackgroundModeManager::NumberOfBackgroundModeData() {
326  return background_mode_data_.size();
327}
328
329///////////////////////////////////////////////////////////////////////////////
330//  BackgroundModeManager, content::NotificationObserver overrides
331void BackgroundModeManager::Observe(
332    int type,
333    const content::NotificationSource& source,
334    const content::NotificationDetails& details) {
335  switch (type) {
336    case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED:
337      // Extensions are loaded, so we don't need to manually keep the browser
338      // process alive any more when running in no-startup-window mode.
339      DecrementKeepAliveCountForStartup();
340      break;
341    case chrome::NOTIFICATION_APP_TERMINATING:
342      // Make sure we aren't still keeping the app alive (only happens if we
343      // don't receive an EXTENSIONS_READY notification for some reason).
344      DecrementKeepAliveCountForStartup();
345      // Performing an explicit shutdown, so exit background mode (does nothing
346      // if we aren't in background mode currently).
347      EndBackgroundMode();
348      // Shutting down, so don't listen for any more notifications so we don't
349      // try to re-enter/exit background mode again.
350      registrar_.RemoveAll();
351      for (BackgroundModeInfoMap::iterator it =
352               background_mode_data_.begin();
353           it != background_mode_data_.end();
354           ++it) {
355        it->second->applications_->RemoveObserver(this);
356      }
357      break;
358    default:
359      NOTREACHED();
360      break;
361  }
362}
363
364void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
365  if (IsBackgroundModePrefEnabled())
366    EnableBackgroundMode();
367  else
368    DisableBackgroundMode();
369}
370
371///////////////////////////////////////////////////////////////////////////////
372//  BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
373void BackgroundModeManager::OnApplicationDataChanged(
374    const Extension* extension, Profile* profile) {
375  UpdateStatusTrayIconContextMenu();
376}
377
378void BackgroundModeManager::OnApplicationListChanged(Profile* profile) {
379  if (!IsBackgroundModePrefEnabled())
380    return;
381
382  // Update the profile cache with the fact whether background apps are running
383  // for this profile.
384  size_t profile_index = profile_cache_->GetIndexOfProfileWithPath(
385      profile->GetPath());
386  if (profile_index != std::string::npos) {
387    profile_cache_->SetBackgroundStatusOfProfileAtIndex(
388        profile_index, GetBackgroundAppCountForProfile(profile) != 0);
389  }
390
391  if (!ShouldBeInBackgroundMode()) {
392    // We've uninstalled our last background app, make sure we exit background
393    // mode and no longer launch on startup.
394    EnableLaunchOnStartup(false);
395    EndBackgroundMode();
396  } else {
397    // We have at least one background app running - make sure we're in
398    // background mode.
399    if (!in_background_mode_) {
400      // We're entering background mode - make sure we have launch-on-startup
401      // enabled. On Mac, the platform-specific code tracks whether the user
402      // has deleted a login item in the past, and if so, no login item will
403      // be created (to avoid overriding the specific user action).
404      EnableLaunchOnStartup(true);
405
406      StartBackgroundMode();
407    }
408    // List of applications changed so update the UI.
409    UpdateStatusTrayIconContextMenu();
410
411    // Notify the user about any new applications.
412    BackgroundModeData* bmd = GetBackgroundModeData(profile);
413    std::set<const extensions::Extension*> new_apps =
414        bmd->GetNewBackgroundApps();
415    for (std::set<const extensions::Extension*>::const_iterator it =
416             new_apps.begin(); it != new_apps.end(); ++it) {
417      OnBackgroundAppInstalled(*it);
418    }
419  }
420}
421
422///////////////////////////////////////////////////////////////////////////////
423//  BackgroundModeManager, ProfileInfoCacheObserver overrides
424void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) {
425  ProfileInfoCache& cache =
426      g_browser_process->profile_manager()->GetProfileInfoCache();
427  base::string16 profile_name = cache.GetNameOfProfileAtIndex(
428      cache.GetIndexOfProfileWithPath(profile_path));
429  // At this point, the profile should be registered with the background mode
430  // manager, but when it's actually added to the cache is when its name is
431  // set so we need up to update that with the background_mode_data.
432  for (BackgroundModeInfoMap::const_iterator it =
433       background_mode_data_.begin();
434       it != background_mode_data_.end();
435       ++it) {
436    if (it->first->GetPath() == profile_path) {
437      it->second->SetName(profile_name);
438      UpdateStatusTrayIconContextMenu();
439      return;
440    }
441  }
442}
443
444void BackgroundModeManager::OnProfileWillBeRemoved(
445    const base::FilePath& profile_path) {
446  ProfileInfoCache& cache =
447      g_browser_process->profile_manager()->GetProfileInfoCache();
448  base::string16 profile_name = cache.GetNameOfProfileAtIndex(
449      cache.GetIndexOfProfileWithPath(profile_path));
450  // Remove the profile from our map of profiles.
451  BackgroundModeInfoMap::iterator it =
452      GetBackgroundModeIterator(profile_name);
453  // If a profile isn't running a background app, it may not be in the map.
454  if (it != background_mode_data_.end()) {
455    it->second->applications_->RemoveObserver(this);
456    background_mode_data_.erase(it);
457    // If there are no background mode profiles any longer, then turn off
458    // background mode.
459    if (!ShouldBeInBackgroundMode()) {
460      EnableLaunchOnStartup(false);
461      EndBackgroundMode();
462    }
463    UpdateStatusTrayIconContextMenu();
464  }
465}
466
467void 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
487void 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
525void BackgroundModeManager::DecrementKeepAliveCountForStartup() {
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(
532        FROM_HERE, base::Bind(&chrome::DecrementKeepAliveCount));
533  }
534}
535
536void 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
554void 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
567void 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
576void 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
585void BackgroundModeManager::SuspendBackgroundMode() {
586  background_mode_suspended_ = true;
587  UpdateKeepAliveAndTrayIcon();
588}
589
590void BackgroundModeManager::ResumeBackgroundMode() {
591  background_mode_suspended_ = false;
592  UpdateKeepAliveAndTrayIcon();
593}
594
595void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
596  if (in_background_mode_ && !background_mode_suspended_) {
597    if (!keeping_alive_) {
598      keeping_alive_ = true;
599      chrome::IncrementKeepAliveCount();
600    }
601    CreateStatusTrayIcon();
602    return;
603  }
604
605  RemoveStatusTrayIcon();
606  if (keeping_alive_) {
607    keeping_alive_ = false;
608    chrome::DecrementKeepAliveCount();
609  }
610}
611
612void BackgroundModeManager::OnBrowserAdded(Browser* browser) {
613  ResumeBackgroundMode();
614}
615
616int 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
629int BackgroundModeManager::GetBackgroundAppCountForProfile(
630    Profile* const profile) const {
631  BackgroundModeData* bmd = GetBackgroundModeData(profile);
632  return bmd->GetBackgroundAppCount();
633}
634
635bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
636  return IsBackgroundModePrefEnabled() &&
637      (GetBackgroundAppCount() > 0 || keep_alive_for_test_);
638}
639
640void 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
657void BackgroundModeManager::CreateStatusTrayIcon() {
658  // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
659  // Chrome and Mac can use the dock icon instead.
660
661  // Since there are multiple profiles which share the status tray, we now
662  // use the browser process to keep track of it.
663#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
664  if (!status_tray_)
665    status_tray_ = g_browser_process->status_tray();
666#endif
667
668  // If the platform doesn't support status icons, or we've already created
669  // our status icon, just return.
670  if (!status_tray_ || status_icon_)
671    return;
672
673  // TODO(rlp): Status tray icon should have submenus for each profile.
674  gfx::ImageSkia* image_skia = ui::ResourceBundle::GetSharedInstance().
675      GetImageSkiaNamed(IDR_STATUS_TRAY_ICON);
676
677  status_icon_ = status_tray_->CreateStatusIcon(
678      StatusTray::BACKGROUND_MODE_ICON,
679      *image_skia,
680      l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
681  if (!status_icon_)
682    return;
683  UpdateStatusTrayIconContextMenu();
684}
685
686void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
687  // Ensure we have a tray icon if appropriate.
688  UpdateKeepAliveAndTrayIcon();
689
690  // If we don't have a status icon or one could not be created succesfully,
691  // then no need to continue the update.
692  if (!status_icon_)
693    return;
694
695  // We should only get here if we have a profile loaded, or if we're running
696  // in test mode.
697  if (background_mode_data_.empty()) {
698    DCHECK(keep_alive_for_test_);
699    return;
700  }
701
702  // We are building a new menu. Reset the Command IDs.
703  command_id_extension_vector_.clear();
704
705  // Clear the submenus too since we will be creating new ones.
706  submenus.clear();
707
708  // TODO(rlp): Add current profile color or indicator.
709  // Create a context menu item for Chrome.
710  scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
711  // Add About item
712  menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
713  menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
714  menu->AddSeparator(ui::NORMAL_SEPARATOR);
715
716  if (profile_cache_->GetNumberOfProfiles() > 1) {
717    std::vector<BackgroundModeData*> bmd_vector;
718    for (BackgroundModeInfoMap::iterator it =
719         background_mode_data_.begin();
720         it != background_mode_data_.end();
721         ++it) {
722       bmd_vector.push_back(it->second.get());
723    }
724    std::sort(bmd_vector.begin(), bmd_vector.end(),
725              &BackgroundModeData::BackgroundModeDataCompare);
726    int profiles_with_apps = 0;
727    for (std::vector<BackgroundModeData*>::const_iterator bmd_it =
728         bmd_vector.begin();
729         bmd_it != bmd_vector.end();
730         ++bmd_it) {
731      BackgroundModeData* bmd = *bmd_it;
732      // We should only display the profile in the status icon if it has at
733      // least one background app.
734      if (bmd->GetBackgroundAppCount() > 0) {
735        StatusIconMenuModel* submenu = new StatusIconMenuModel(bmd);
736        // The submenu constructor caller owns the lifetime of the submenu.
737        // The containing menu does not handle the lifetime.
738        submenus.push_back(submenu);
739        bmd->BuildProfileMenu(submenu, menu.get());
740        profiles_with_apps++;
741      }
742    }
743    // We should only be displaying the status tray icon if there is at least
744    // one profile with a background app.
745    DCHECK_GT(profiles_with_apps, 0);
746  } else {
747    // We should only have one profile in the cache if we are not
748    // using multi-profiles. If keep_alive_for_test_ is set, then we may not
749    // have any profiles in the cache.
750    DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) ||
751           keep_alive_for_test_);
752    background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), NULL);
753  }
754
755  menu->AddSeparator(ui::NORMAL_SEPARATOR);
756  menu->AddCheckItemWithStringId(
757      IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
758      IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
759  menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
760                            true);
761
762  PrefService* service = g_browser_process->local_state();
763  DCHECK(service);
764  bool enabled =
765      service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled);
766  menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
767                            enabled);
768
769  menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
770
771  context_menu_ = menu.get();
772  status_icon_->SetContextMenu(menu.Pass());
773}
774
775void BackgroundModeManager::RemoveStatusTrayIcon() {
776  if (status_icon_)
777    status_tray_->RemoveStatusIcon(status_icon_);
778  status_icon_ = NULL;
779  context_menu_ = NULL;
780}
781
782BackgroundModeManager::BackgroundModeData*
783BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const {
784  DCHECK(background_mode_data_.find(profile) != background_mode_data_.end());
785  return background_mode_data_.find(profile)->second.get();
786}
787
788BackgroundModeManager::BackgroundModeInfoMap::iterator
789BackgroundModeManager::GetBackgroundModeIterator(
790    const base::string16& profile_name) {
791  BackgroundModeInfoMap::iterator profile_it =
792      background_mode_data_.end();
793  for (BackgroundModeInfoMap::iterator it =
794       background_mode_data_.begin();
795       it != background_mode_data_.end();
796       ++it) {
797    if (it->second->name() == profile_name) {
798      profile_it = it;
799    }
800  }
801  return profile_it;
802}
803
804bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
805  PrefService* service = g_browser_process->local_state();
806  DCHECK(service);
807  return service->GetBoolean(prefs::kBackgroundModeEnabled);
808}
809