background_mode_manager.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <algorithm>
6#include <string>
7#include <vector>
8
9#include "base/base_paths.h"
10#include "base/bind.h"
11#include "base/command_line.h"
12#include "base/logging.h"
13#include "base/prefs/pref_registry_simple.h"
14#include "base/prefs/pref_service.h"
15#include "base/strings/utf_string_conversions.h"
16#include "chrome/app/chrome_command_ids.h"
17#include "chrome/browser/background/background_application_list_model.h"
18#include "chrome/browser/background/background_mode_manager.h"
19#include "chrome/browser/browser_process.h"
20#include "chrome/browser/browser_shutdown.h"
21#include "chrome/browser/chrome_notification_types.h"
22#include "chrome/browser/extensions/extension_service.h"
23#include "chrome/browser/lifetime/application_lifetime.h"
24#include "chrome/browser/profiles/profile.h"
25#include "chrome/browser/profiles/profile_info_cache.h"
26#include "chrome/browser/profiles/profile_manager.h"
27#include "chrome/browser/status_icons/status_icon.h"
28#include "chrome/browser/status_icons/status_tray.h"
29#include "chrome/browser/ui/browser.h"
30#include "chrome/browser/ui/browser_commands.h"
31#include "chrome/browser/ui/browser_finder.h"
32#include "chrome/browser/ui/browser_list.h"
33#include "chrome/browser/ui/chrome_pages.h"
34#include "chrome/browser/ui/extensions/application_launch.h"
35#include "chrome/browser/ui/host_desktop.h"
36#include "chrome/common/chrome_constants.h"
37#include "chrome/common/chrome_switches.h"
38#include "chrome/common/extensions/extension_constants.h"
39#include "chrome/common/extensions/manifest_url_handler.h"
40#include "chrome/common/pref_names.h"
41#include "content/public/browser/notification_service.h"
42#include "content/public/browser/user_metrics.h"
43#include "extensions/browser/extension_system.h"
44#include "extensions/common/extension.h"
45#include "extensions/common/permissions/permission_set.h"
46#include "grit/chrome_unscaled_resources.h"
47#include "grit/chromium_strings.h"
48#include "grit/generated_resources.h"
49#include "ui/base/l10n/l10n_util.h"
50#include "ui/base/resource/resource_bundle.h"
51
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
164// static
165bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
166    const BackgroundModeData* bmd1,
167    const BackgroundModeData* bmd2) {
168  return bmd1->name_ < bmd2->name_;
169}
170
171
172///////////////////////////////////////////////////////////////////////////////
173//  BackgroundModeManager, public
174BackgroundModeManager::BackgroundModeManager(
175    CommandLine* command_line,
176    ProfileInfoCache* profile_cache)
177    : profile_cache_(profile_cache),
178      status_tray_(NULL),
179      status_icon_(NULL),
180      context_menu_(NULL),
181      in_background_mode_(false),
182      keep_alive_for_startup_(false),
183      keep_alive_for_test_(false),
184      background_mode_suspended_(false),
185      keeping_alive_(false) {
186  // We should never start up if there is no browser process or if we are
187  // currently quitting.
188  CHECK(g_browser_process != NULL);
189  CHECK(!browser_shutdown::IsTryingToQuit());
190
191  // Add self as an observer for the profile info cache so we know when profiles
192  // are deleted and their names change.
193  profile_cache_->AddObserver(this);
194
195  // Listen for the background mode preference changing.
196  if (g_browser_process->local_state()) {  // Skip for unit tests
197    pref_registrar_.Init(g_browser_process->local_state());
198    pref_registrar_.Add(
199        prefs::kBackgroundModeEnabled,
200        base::Bind(&BackgroundModeManager::OnBackgroundModeEnabledPrefChanged,
201                   base::Unretained(this)));
202  }
203
204  // Keep the browser alive until extensions are done loading - this is needed
205  // by the --no-startup-window flag. We want to stay alive until we load
206  // extensions, at which point we should either run in background mode (if
207  // there are background apps) or exit if there are none.
208  if (command_line->HasSwitch(switches::kNoStartupWindow)) {
209    keep_alive_for_startup_ = true;
210    chrome::IncrementKeepAliveCount();
211  } else {
212    // Otherwise, start with background mode suspended in case we're launching
213    // in a mode that doesn't open a browser window. It will be resumed when the
214    // first browser window is opened.
215    SuspendBackgroundMode();
216  }
217
218  // If the -keep-alive-for-test flag is passed, then always keep chrome running
219  // in the background until the user explicitly terminates it.
220  if (command_line->HasSwitch(switches::kKeepAliveForTest))
221    keep_alive_for_test_ = true;
222
223  if (ShouldBeInBackgroundMode())
224    StartBackgroundMode();
225
226  // Listen for the application shutting down so we can decrement our KeepAlive
227  // count.
228  registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
229                 content::NotificationService::AllSources());
230  BrowserList::AddObserver(this);
231}
232
233BackgroundModeManager::~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
252void 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
262void 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
300void BackgroundModeManager::LaunchBackgroundApplication(
301    Profile* profile,
302    const Extension* extension) {
303  OpenApplication(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB));
304}
305
306bool BackgroundModeManager::IsBackgroundModeActive() {
307  return in_background_mode_;
308}
309
310int BackgroundModeManager::NumberOfBackgroundModeData() {
311  return background_mode_data_.size();
312}
313
314///////////////////////////////////////////////////////////////////////////////
315//  BackgroundModeManager, content::NotificationObserver overrides
316void 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      DecrementKeepAliveCountForStartup();
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      DecrementKeepAliveCountForStartup();
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
380void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
381  if (IsBackgroundModePrefEnabled())
382    EnableBackgroundMode();
383  else
384    DisableBackgroundMode();
385}
386
387///////////////////////////////////////////////////////////////////////////////
388//  BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
389void BackgroundModeManager::OnApplicationDataChanged(
390    const Extension* extension, Profile* profile) {
391  UpdateStatusTrayIconContextMenu();
392}
393
394void 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
431void 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
451void 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    it->second->applications_->RemoveObserver(this);
463    background_mode_data_.erase(it);
464    // If there are no background mode profiles any longer, then turn off
465    // background mode.
466    if (!ShouldBeInBackgroundMode()) {
467      EnableLaunchOnStartup(false);
468      EndBackgroundMode();
469    }
470    UpdateStatusTrayIconContextMenu();
471  }
472}
473
474void BackgroundModeManager::OnProfileNameChanged(
475    const base::FilePath& profile_path,
476    const base::string16& old_profile_name) {
477  ProfileInfoCache& cache =
478      g_browser_process->profile_manager()->GetProfileInfoCache();
479  base::string16 new_profile_name = cache.GetNameOfProfileAtIndex(
480      cache.GetIndexOfProfileWithPath(profile_path));
481  BackgroundModeInfoMap::const_iterator it =
482      GetBackgroundModeIterator(old_profile_name);
483  // We check that the returned iterator is valid due to unittests, but really
484  // this should only be called on profiles already known by the background
485  // mode manager.
486  if (it != background_mode_data_.end()) {
487    it->second->SetName(new_profile_name);
488    UpdateStatusTrayIconContextMenu();
489  }
490}
491
492///////////////////////////////////////////////////////////////////////////////
493//  BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
494void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) {
495  // When a browser window is necessary, we use the first profile. The windows
496  // opened for these commands are not profile-specific, so any profile would
497  // work and the first is convenient.
498  BackgroundModeData* bmd = background_mode_data_.begin()->second.get();
499  switch (command_id) {
500    case IDC_ABOUT:
501      chrome::ShowAboutChrome(bmd->GetBrowserWindow());
502      break;
503    case IDC_TASK_MANAGER:
504      chrome::OpenTaskManager(bmd->GetBrowserWindow());
505      break;
506    case IDC_EXIT:
507      content::RecordAction(UserMetricsAction("Exit"));
508      chrome::CloseAllBrowsers();
509      break;
510    case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: {
511      // Background mode must already be enabled (as otherwise this menu would
512      // not be visible).
513      DCHECK(IsBackgroundModePrefEnabled());
514      DCHECK(chrome::WillKeepAlive());
515
516      // Set the background mode pref to "disabled" - the resulting notification
517      // will result in a call to DisableBackgroundMode().
518      PrefService* service = g_browser_process->local_state();
519      DCHECK(service);
520      service->SetBoolean(prefs::kBackgroundModeEnabled, false);
521      break;
522    }
523    default:
524      bmd->ExecuteCommand(command_id, event_flags);
525      break;
526  }
527}
528
529
530///////////////////////////////////////////////////////////////////////////////
531//  BackgroundModeManager, private
532void BackgroundModeManager::DecrementKeepAliveCountForStartup() {
533  if (keep_alive_for_startup_) {
534    keep_alive_for_startup_ = false;
535    // We call this via the message queue to make sure we don't try to end
536    // keep-alive (which can shutdown Chrome) before the message loop has
537    // started.
538    base::MessageLoop::current()->PostTask(
539        FROM_HERE, base::Bind(&chrome::DecrementKeepAliveCount));
540  }
541}
542
543void BackgroundModeManager::StartBackgroundMode() {
544  DCHECK(ShouldBeInBackgroundMode());
545  // Don't bother putting ourselves in background mode if we're already there
546  // or if background mode is disabled.
547  if (in_background_mode_)
548    return;
549
550  // Mark ourselves as running in background mode.
551  in_background_mode_ = true;
552
553  UpdateKeepAliveAndTrayIcon();
554
555  content::NotificationService::current()->Notify(
556      chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
557      content::Source<BackgroundModeManager>(this),
558      content::Details<bool>(&in_background_mode_));
559}
560
561void BackgroundModeManager::EndBackgroundMode() {
562  if (!in_background_mode_)
563    return;
564  in_background_mode_ = false;
565
566  UpdateKeepAliveAndTrayIcon();
567
568  content::NotificationService::current()->Notify(
569      chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
570      content::Source<BackgroundModeManager>(this),
571      content::Details<bool>(&in_background_mode_));
572}
573
574void BackgroundModeManager::EnableBackgroundMode() {
575  DCHECK(IsBackgroundModePrefEnabled());
576  // If background mode should be enabled, but isn't, turn it on.
577  if (!in_background_mode_ && ShouldBeInBackgroundMode()) {
578    StartBackgroundMode();
579    EnableLaunchOnStartup(true);
580  }
581}
582
583void BackgroundModeManager::DisableBackgroundMode() {
584  DCHECK(!IsBackgroundModePrefEnabled());
585  // If background mode is currently enabled, turn it off.
586  if (in_background_mode_) {
587    EndBackgroundMode();
588    EnableLaunchOnStartup(false);
589  }
590}
591
592void BackgroundModeManager::SuspendBackgroundMode() {
593  background_mode_suspended_ = true;
594  UpdateKeepAliveAndTrayIcon();
595}
596
597void BackgroundModeManager::ResumeBackgroundMode() {
598  background_mode_suspended_ = false;
599  UpdateKeepAliveAndTrayIcon();
600}
601
602void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
603  if (in_background_mode_ && !background_mode_suspended_) {
604    if (!keeping_alive_) {
605      keeping_alive_ = true;
606      chrome::IncrementKeepAliveCount();
607    }
608    CreateStatusTrayIcon();
609    return;
610  }
611
612  RemoveStatusTrayIcon();
613  if (keeping_alive_) {
614    keeping_alive_ = false;
615    chrome::DecrementKeepAliveCount();
616  }
617}
618
619void BackgroundModeManager::OnBrowserAdded(Browser* browser) {
620  ResumeBackgroundMode();
621}
622
623int BackgroundModeManager::GetBackgroundAppCount() const {
624  int count = 0;
625  // Walk the BackgroundModeData for all profiles and count the number of apps.
626  for (BackgroundModeInfoMap::const_iterator it =
627       background_mode_data_.begin();
628       it != background_mode_data_.end();
629       ++it) {
630    count += it->second->GetBackgroundAppCount();
631  }
632  DCHECK(count >= 0);
633  return count;
634}
635
636int BackgroundModeManager::GetBackgroundAppCountForProfile(
637    Profile* const profile) const {
638  BackgroundModeData* bmd = GetBackgroundModeData(profile);
639  return bmd->GetBackgroundAppCount();
640}
641
642bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
643  return IsBackgroundModePrefEnabled() &&
644      (GetBackgroundAppCount() > 0 || keep_alive_for_test_);
645}
646
647void BackgroundModeManager::OnBackgroundAppInstalled(
648    const Extension* extension) {
649  // Background mode is disabled - don't do anything.
650  if (!IsBackgroundModePrefEnabled())
651    return;
652
653  // Ensure we have a tray icon (needed so we can display the app-installed
654  // notification below).
655  EnableBackgroundMode();
656  ResumeBackgroundMode();
657
658  // Notify the user that a background app has been installed.
659  if (extension) {  // NULL when called by unit tests.
660    DisplayAppInstalledNotification(extension);
661  }
662}
663
664void BackgroundModeManager::CheckReloadStatus(
665    const Extension* extension,
666    bool* is_being_reloaded) {
667    // Walk the BackgroundModeData for all profiles to see if one of their
668    // extensions is being reloaded.
669    for (BackgroundModeInfoMap::const_iterator it =
670             background_mode_data_.begin();
671         it != background_mode_data_.end();
672         ++it) {
673      Profile* profile = it->first;
674      // If the extension is being reloaded, no need to show a notification.
675      if (profile->GetExtensionService()->IsBeingReloaded(extension->id()))
676        *is_being_reloaded = true;
677    }
678}
679
680void BackgroundModeManager::CreateStatusTrayIcon() {
681  // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
682  // Chrome and Mac can use the dock icon instead.
683
684  // Since there are multiple profiles which share the status tray, we now
685  // use the browser process to keep track of it.
686#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
687  if (!status_tray_)
688    status_tray_ = g_browser_process->status_tray();
689#endif
690
691  // If the platform doesn't support status icons, or we've already created
692  // our status icon, just return.
693  if (!status_tray_ || status_icon_)
694    return;
695
696  // TODO(rlp): Status tray icon should have submenus for each profile.
697  gfx::ImageSkia* image_skia = ui::ResourceBundle::GetSharedInstance().
698      GetImageSkiaNamed(IDR_STATUS_TRAY_ICON);
699
700  status_icon_ = status_tray_->CreateStatusIcon(
701      StatusTray::BACKGROUND_MODE_ICON,
702      *image_skia,
703      l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
704  if (!status_icon_)
705    return;
706  UpdateStatusTrayIconContextMenu();
707}
708
709void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
710  // Ensure we have a tray icon if appropriate.
711  UpdateKeepAliveAndTrayIcon();
712
713  // If we don't have a status icon or one could not be created succesfully,
714  // then no need to continue the update.
715  if (!status_icon_)
716    return;
717
718  // We should only get here if we have a profile loaded, or if we're running
719  // in test mode.
720  if (background_mode_data_.empty()) {
721    DCHECK(keep_alive_for_test_);
722    return;
723  }
724
725  // We are building a new menu. Reset the Command IDs.
726  command_id_extension_vector_.clear();
727
728  // Clear the submenus too since we will be creating new ones.
729  submenus.clear();
730
731  // TODO(rlp): Add current profile color or indicator.
732  // Create a context menu item for Chrome.
733  scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
734  // Add About item
735  menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
736  menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
737  menu->AddSeparator(ui::NORMAL_SEPARATOR);
738
739  if (profile_cache_->GetNumberOfProfiles() > 1) {
740    std::vector<BackgroundModeData*> bmd_vector;
741    for (BackgroundModeInfoMap::iterator it =
742         background_mode_data_.begin();
743         it != background_mode_data_.end();
744         ++it) {
745       bmd_vector.push_back(it->second.get());
746    }
747    std::sort(bmd_vector.begin(), bmd_vector.end(),
748              &BackgroundModeData::BackgroundModeDataCompare);
749    int profiles_with_apps = 0;
750    for (std::vector<BackgroundModeData*>::const_iterator bmd_it =
751         bmd_vector.begin();
752         bmd_it != bmd_vector.end();
753         ++bmd_it) {
754      BackgroundModeData* bmd = *bmd_it;
755      // We should only display the profile in the status icon if it has at
756      // least one background app.
757      if (bmd->GetBackgroundAppCount() > 0) {
758        StatusIconMenuModel* submenu = new StatusIconMenuModel(bmd);
759        // The submenu constructor caller owns the lifetime of the submenu.
760        // The containing menu does not handle the lifetime.
761        submenus.push_back(submenu);
762        bmd->BuildProfileMenu(submenu, menu.get());
763        profiles_with_apps++;
764      }
765    }
766    // We should only be displaying the status tray icon if there is at least
767    // one profile with a background app.
768    DCHECK_GT(profiles_with_apps, 0);
769  } else {
770    // We should only have one profile in the cache if we are not
771    // using multi-profiles. If keep_alive_for_test_ is set, then we may not
772    // have any profiles in the cache.
773    DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) ||
774           keep_alive_for_test_);
775    background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), NULL);
776  }
777
778  menu->AddSeparator(ui::NORMAL_SEPARATOR);
779  menu->AddCheckItemWithStringId(
780      IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
781      IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
782  menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
783                            true);
784
785  PrefService* service = g_browser_process->local_state();
786  DCHECK(service);
787  bool enabled =
788      service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled);
789  menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
790                            enabled);
791
792  menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
793
794  context_menu_ = menu.get();
795  status_icon_->SetContextMenu(menu.Pass());
796}
797
798void BackgroundModeManager::RemoveStatusTrayIcon() {
799  if (status_icon_)
800    status_tray_->RemoveStatusIcon(status_icon_);
801  status_icon_ = NULL;
802  context_menu_ = NULL;
803}
804
805BackgroundModeManager::BackgroundModeData*
806BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const {
807  DCHECK(background_mode_data_.find(profile) != background_mode_data_.end());
808  return background_mode_data_.find(profile)->second.get();
809}
810
811BackgroundModeManager::BackgroundModeInfoMap::iterator
812BackgroundModeManager::GetBackgroundModeIterator(
813    const base::string16& profile_name) {
814  BackgroundModeInfoMap::iterator profile_it =
815      background_mode_data_.end();
816  for (BackgroundModeInfoMap::iterator it =
817       background_mode_data_.begin();
818       it != background_mode_data_.end();
819       ++it) {
820    if (it->second->name() == profile_name) {
821      profile_it = it;
822    }
823  }
824  return profile_it;
825}
826
827bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
828  PrefService* service = g_browser_process->local_state();
829  DCHECK(service);
830  return service->GetBoolean(prefs::kBackgroundModeEnabled);
831}
832