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