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