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