Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / background / background_mode_manager.cc
blob4e4a2e76ec839cfebad839e7a849bc3d18e43efe
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/permissions/permission_set.h"
54 #include "grit/chrome_unscaled_resources.h"
55 #include "ui/base/l10n/l10n_util.h"
56 #include "ui/base/resource/resource_bundle.h"
58 #if defined(OS_WIN)
59 #include "components/browser_watcher/exit_funnel_win.h"
60 #endif
62 using base::UserMetricsAction;
63 using extensions::Extension;
64 using extensions::UpdatedExtensionPermissionsInfo;
66 namespace {
68 const int kInvalidExtensionIndex = -1;
70 // Records histogram about which auto-launch pattern (if any) was used to launch
71 // the current process based on |command_line|.
72 void RecordAutoLaunchState(const base::CommandLine& command_line) {
73 enum AutoLaunchState {
74 AUTO_LAUNCH_NONE = 0,
75 AUTO_LAUNCH_BACKGROUND = 1,
76 AUTO_LAUNCH_FOREGROUND = 2,
77 AUTO_LAUNCH_FOREGROUND_USELESS = 3,
78 AUTO_LAUNCH_NUM_STATES
79 } auto_launch_state = AUTO_LAUNCH_NONE;
81 if (command_line.HasSwitch(switches::kNoStartupWindow))
82 auto_launch_state = AUTO_LAUNCH_BACKGROUND;
84 if (command_line.HasSwitch(switches::kAutoLaunchAtStartup)) {
85 // The only purpose of kAutoLaunchAtStartup is to override a background
86 // auto-launch from kNoStartupWindow into a foreground auto-launch. It's a
87 // meaningless switch on its own.
88 if (auto_launch_state == AUTO_LAUNCH_BACKGROUND) {
89 auto_launch_state = AUTO_LAUNCH_FOREGROUND;
90 } else {
91 auto_launch_state = AUTO_LAUNCH_FOREGROUND_USELESS;
95 // Observe the AutoLaunchStates in the wild. According to the platform-
96 // specific implementations of EnableLaunchOnStartup(), we'd expect only Mac
97 // and Windows to have any sort of AutoLaunchState and only Windows should use
98 // FOREGROUND if at all (it was only used by a deprecated experiment and a
99 // master pref which may not be used much). Tighten up auto-launch settings
100 // based on the result of usage in the wild.
101 UMA_HISTOGRAM_ENUMERATION("BackgroundMode.OnStartup.AutoLaunchState",
102 auto_launch_state, AUTO_LAUNCH_NUM_STATES);
105 } // namespace
107 BackgroundModeManager::BackgroundModeData::BackgroundModeData(
108 Profile* profile,
109 CommandIdExtensionVector* command_id_extension_vector)
110 : applications_(new BackgroundApplicationListModel(profile)),
111 profile_(profile),
112 command_id_extension_vector_(command_id_extension_vector) {
115 BackgroundModeManager::BackgroundModeData::~BackgroundModeData() {
118 ///////////////////////////////////////////////////////////////////////////////
119 // BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
120 void BackgroundModeManager::BackgroundModeData::ExecuteCommand(
121 int command_id,
122 int event_flags) {
123 switch (command_id) {
124 case IDC_MinimumLabelValue:
125 // Do nothing. This is just a label.
126 break;
127 default:
128 // Launch the app associated with this Command ID.
129 int extension_index = command_id_extension_vector_->at(command_id);
130 if (extension_index != kInvalidExtensionIndex) {
131 const Extension* extension =
132 applications_->GetExtension(extension_index);
133 BackgroundModeManager::LaunchBackgroundApplication(profile_, extension);
135 break;
139 Browser* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
140 chrome::HostDesktopType host_desktop_type = chrome::GetActiveDesktop();
141 Browser* browser = chrome::FindLastActiveWithProfile(profile_,
142 host_desktop_type);
143 return browser ? browser : chrome::OpenEmptyWindow(profile_,
144 host_desktop_type);
147 int BackgroundModeManager::BackgroundModeData::GetBackgroundAppCount() const {
148 return applications_->size();
151 void BackgroundModeManager::BackgroundModeData::BuildProfileMenu(
152 StatusIconMenuModel* menu,
153 StatusIconMenuModel* containing_menu) {
154 int position = 0;
155 // When there are no background applications, we want to display
156 // just a label stating that none are running.
157 if (applications_->size() < 1) {
158 menu->AddItemWithStringId(IDC_MinimumLabelValue,
159 IDS_BACKGROUND_APP_NOT_INSTALLED);
160 menu->SetCommandIdEnabled(IDC_MinimumLabelValue, false);
161 } else {
162 for (extensions::ExtensionList::const_iterator cursor =
163 applications_->begin();
164 cursor != applications_->end();
165 ++cursor, ++position) {
166 const gfx::ImageSkia* icon = applications_->GetIcon(cursor->get());
167 DCHECK(position == applications_->GetPosition(cursor->get()));
168 const std::string& name = (*cursor)->name();
169 int command_id = command_id_extension_vector_->size();
170 // Check that the command ID is within the dynamic range.
171 DCHECK(command_id < IDC_MinimumLabelValue);
172 command_id_extension_vector_->push_back(position);
173 menu->AddItem(command_id, base::UTF8ToUTF16(name));
174 if (icon)
175 menu->SetIcon(menu->GetItemCount() - 1, gfx::Image(*icon));
177 // Component extensions with background that do not have an options page
178 // will cause this menu item to go to the extensions page with an
179 // absent component extension.
181 // Ideally, we would remove this item, but this conflicts with the user
182 // model where this menu shows the extensions with background.
184 // The compromise is to disable the item, avoiding the non-actionable
185 // navigate to the extensions page and preserving the user model.
186 if ((*cursor)->location() == extensions::Manifest::COMPONENT) {
187 GURL options_page =
188 extensions::OptionsPageInfo::GetOptionsPage(cursor->get());
189 if (!options_page.is_valid())
190 menu->SetCommandIdEnabled(command_id, false);
194 if (containing_menu) {
195 int menu_command_id = command_id_extension_vector_->size();
196 // Check that the command ID is within the dynamic range.
197 DCHECK(menu_command_id < IDC_MinimumLabelValue);
198 command_id_extension_vector_->push_back(kInvalidExtensionIndex);
199 containing_menu->AddSubMenu(menu_command_id, name_, menu);
203 void BackgroundModeManager::BackgroundModeData::SetName(
204 const base::string16& new_profile_name) {
205 name_ = new_profile_name;
208 base::string16 BackgroundModeManager::BackgroundModeData::name() {
209 return name_;
212 std::set<const extensions::Extension*>
213 BackgroundModeManager::BackgroundModeData::GetNewBackgroundApps() {
214 std::set<const extensions::Extension*> new_apps;
216 // Copy all current extensions into our list of |current_extensions_|.
217 for (extensions::ExtensionList::const_iterator it = applications_->begin();
218 it != applications_->end(); ++it) {
219 const extensions::ExtensionId& id = (*it)->id();
220 if (current_extensions_.count(id) == 0) {
221 // Not found in our set yet - add it and maybe return as a previously
222 // unseen extension.
223 current_extensions_.insert(id);
224 // If this application has been newly loaded after the initial startup,
225 // notify the user.
226 if (applications_->is_ready()) {
227 const extensions::Extension* extension = (*it).get();
228 new_apps.insert(extension);
232 return new_apps;
235 // static
236 bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
237 const BackgroundModeData* bmd1,
238 const BackgroundModeData* bmd2) {
239 return bmd1->name_ < bmd2->name_;
243 ///////////////////////////////////////////////////////////////////////////////
244 // BackgroundModeManager, public
245 BackgroundModeManager::BackgroundModeManager(
246 const base::CommandLine& command_line,
247 ProfileInfoCache* profile_cache)
248 : profile_cache_(profile_cache),
249 status_tray_(NULL),
250 status_icon_(NULL),
251 context_menu_(NULL),
252 in_background_mode_(false),
253 keep_alive_for_startup_(false),
254 keep_alive_for_test_(false),
255 background_mode_suspended_(false),
256 keeping_alive_(false) {
257 // We should never start up if there is no browser process or if we are
258 // currently quitting.
259 CHECK(g_browser_process != NULL);
260 CHECK(!browser_shutdown::IsTryingToQuit());
262 // Add self as an observer for the profile info cache so we know when profiles
263 // are deleted and their names change.
264 profile_cache_->AddObserver(this);
266 RecordAutoLaunchState(command_line);
267 UMA_HISTOGRAM_BOOLEAN("BackgroundMode.OnStartup.IsBackgroundModePrefEnabled",
268 IsBackgroundModePrefEnabled());
270 // Listen for the background mode preference changing.
271 if (g_browser_process->local_state()) { // Skip for unit tests
272 pref_registrar_.Init(g_browser_process->local_state());
273 pref_registrar_.Add(
274 prefs::kBackgroundModeEnabled,
275 base::Bind(&BackgroundModeManager::OnBackgroundModeEnabledPrefChanged,
276 base::Unretained(this)));
279 // Keep the browser alive until extensions are done loading - this is needed
280 // by the --no-startup-window flag. We want to stay alive until we load
281 // extensions, at which point we should either run in background mode (if
282 // there are background apps) or exit if there are none.
283 if (command_line.HasSwitch(switches::kNoStartupWindow)) {
284 keep_alive_for_startup_ = true;
285 chrome::IncrementKeepAliveCount();
286 } else {
287 // Otherwise, start with background mode suspended in case we're launching
288 // in a mode that doesn't open a browser window. It will be resumed when the
289 // first browser window is opened.
290 SuspendBackgroundMode();
293 // If the -keep-alive-for-test flag is passed, then always keep chrome running
294 // in the background until the user explicitly terminates it.
295 if (command_line.HasSwitch(switches::kKeepAliveForTest))
296 keep_alive_for_test_ = true;
298 if (ShouldBeInBackgroundMode())
299 StartBackgroundMode();
301 // Listen for the application shutting down so we can decrement our KeepAlive
302 // count.
303 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
304 content::NotificationService::AllSources());
305 BrowserList::AddObserver(this);
308 BackgroundModeManager::~BackgroundModeManager() {
309 // Remove ourselves from the application observer list (only needed by unit
310 // tests since APP_TERMINATING is what does this in a real running system).
311 for (BackgroundModeInfoMap::iterator it =
312 background_mode_data_.begin();
313 it != background_mode_data_.end();
314 ++it) {
315 it->second->applications_->RemoveObserver(this);
317 BrowserList::RemoveObserver(this);
319 // We're going away, so exit background mode (does nothing if we aren't in
320 // background mode currently). This is primarily needed for unit tests,
321 // because in an actual running system we'd get an APP_TERMINATING
322 // notification before being destroyed.
323 EndBackgroundMode();
326 // static
327 void BackgroundModeManager::RegisterPrefs(PrefRegistrySimple* registry) {
328 #if defined(OS_MACOSX)
329 registry->RegisterBooleanPref(prefs::kUserRemovedLoginItem, false);
330 registry->RegisterBooleanPref(prefs::kChromeCreatedLoginItem, false);
331 registry->RegisterBooleanPref(prefs::kMigratedLoginItemPref, false);
332 #endif
333 registry->RegisterBooleanPref(prefs::kBackgroundModeEnabled, true);
336 void BackgroundModeManager::RegisterProfile(Profile* profile) {
337 // We don't want to register multiple times for one profile.
338 DCHECK(background_mode_data_.find(profile) == background_mode_data_.end());
339 BackgroundModeInfo bmd(new BackgroundModeData(profile,
340 &command_id_extension_vector_));
341 background_mode_data_[profile] = bmd;
343 // Initially set the name for this background mode data.
344 size_t index = profile_cache_->GetIndexOfProfileWithPath(profile->GetPath());
345 base::string16 name = l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME);
346 if (index != std::string::npos)
347 name = profile_cache_->GetNameOfProfileAtIndex(index);
348 bmd->SetName(name);
350 // Check for the presence of background apps after all extensions have been
351 // loaded, to handle the case where an extension has been manually removed
352 // while Chrome was not running.
353 registrar_.Add(this,
354 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
355 content::Source<Profile>(profile));
357 bmd->applications_->AddObserver(this);
359 // If we're adding a new profile and running in multi-profile mode, this new
360 // profile should be added to the status icon if one currently exists.
361 if (in_background_mode_ && status_icon_)
362 UpdateStatusTrayIconContextMenu();
365 // static
366 void BackgroundModeManager::LaunchBackgroundApplication(
367 Profile* profile,
368 const Extension* extension) {
369 OpenApplication(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB,
370 extensions::SOURCE_BACKGROUND));
373 bool BackgroundModeManager::IsBackgroundModeActive() {
374 return in_background_mode_;
377 int BackgroundModeManager::NumberOfBackgroundModeData() {
378 return background_mode_data_.size();
381 ///////////////////////////////////////////////////////////////////////////////
382 // BackgroundModeManager, content::NotificationObserver overrides
383 void BackgroundModeManager::Observe(
384 int type,
385 const content::NotificationSource& source,
386 const content::NotificationDetails& details) {
387 switch (type) {
388 case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED:
389 // Extensions are loaded, so we don't need to manually keep the browser
390 // process alive any more when running in no-startup-window mode.
391 DecrementKeepAliveCountForStartup();
392 break;
393 case chrome::NOTIFICATION_APP_TERMINATING:
394 // Make sure we aren't still keeping the app alive (only happens if we
395 // don't receive an EXTENSIONS_READY notification for some reason).
396 DecrementKeepAliveCountForStartup();
397 // Performing an explicit shutdown, so exit background mode (does nothing
398 // if we aren't in background mode currently).
399 EndBackgroundMode();
400 // Shutting down, so don't listen for any more notifications so we don't
401 // try to re-enter/exit background mode again.
402 registrar_.RemoveAll();
403 for (BackgroundModeInfoMap::iterator it =
404 background_mode_data_.begin();
405 it != background_mode_data_.end();
406 ++it) {
407 it->second->applications_->RemoveObserver(this);
409 break;
410 default:
411 NOTREACHED();
412 break;
416 void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
417 if (IsBackgroundModePrefEnabled())
418 EnableBackgroundMode();
419 else
420 DisableBackgroundMode();
423 ///////////////////////////////////////////////////////////////////////////////
424 // BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
425 void BackgroundModeManager::OnApplicationDataChanged(
426 const Extension* extension, Profile* profile) {
427 UpdateStatusTrayIconContextMenu();
430 void BackgroundModeManager::OnApplicationListChanged(Profile* profile) {
431 if (!IsBackgroundModePrefEnabled())
432 return;
434 // Update the profile cache with the fact whether background apps are running
435 // for this profile.
436 size_t profile_index = profile_cache_->GetIndexOfProfileWithPath(
437 profile->GetPath());
438 if (profile_index != std::string::npos) {
439 profile_cache_->SetBackgroundStatusOfProfileAtIndex(
440 profile_index, GetBackgroundAppCountForProfile(profile) != 0);
443 if (!ShouldBeInBackgroundMode()) {
444 // We've uninstalled our last background app, make sure we exit background
445 // mode and no longer launch on startup.
446 EnableLaunchOnStartup(false);
447 EndBackgroundMode();
448 } else {
449 // We have at least one background app running - make sure we're in
450 // background mode.
451 if (!in_background_mode_) {
452 // We're entering background mode - make sure we have launch-on-startup
453 // enabled. On Mac, the platform-specific code tracks whether the user
454 // has deleted a login item in the past, and if so, no login item will
455 // be created (to avoid overriding the specific user action).
456 EnableLaunchOnStartup(true);
458 StartBackgroundMode();
460 // List of applications changed so update the UI.
461 UpdateStatusTrayIconContextMenu();
463 // Notify the user about any new applications.
464 BackgroundModeData* bmd = GetBackgroundModeData(profile);
465 std::set<const extensions::Extension*> new_apps =
466 bmd->GetNewBackgroundApps();
467 for (std::set<const extensions::Extension*>::const_iterator it =
468 new_apps.begin(); it != new_apps.end(); ++it) {
469 OnBackgroundAppInstalled(*it);
474 ///////////////////////////////////////////////////////////////////////////////
475 // BackgroundModeManager, ProfileInfoCacheObserver overrides
476 void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) {
477 ProfileInfoCache& cache =
478 g_browser_process->profile_manager()->GetProfileInfoCache();
479 base::string16 profile_name = cache.GetNameOfProfileAtIndex(
480 cache.GetIndexOfProfileWithPath(profile_path));
481 // At this point, the profile should be registered with the background mode
482 // manager, but when it's actually added to the cache is when its name is
483 // set so we need up to update that with the background_mode_data.
484 for (BackgroundModeInfoMap::const_iterator it =
485 background_mode_data_.begin();
486 it != background_mode_data_.end();
487 ++it) {
488 if (it->first->GetPath() == profile_path) {
489 it->second->SetName(profile_name);
490 UpdateStatusTrayIconContextMenu();
491 return;
496 void BackgroundModeManager::OnProfileWillBeRemoved(
497 const base::FilePath& profile_path) {
498 ProfileInfoCache& cache =
499 g_browser_process->profile_manager()->GetProfileInfoCache();
500 base::string16 profile_name = cache.GetNameOfProfileAtIndex(
501 cache.GetIndexOfProfileWithPath(profile_path));
502 // Remove the profile from our map of profiles.
503 BackgroundModeInfoMap::iterator it =
504 GetBackgroundModeIterator(profile_name);
505 // If a profile isn't running a background app, it may not be in the map.
506 if (it != background_mode_data_.end()) {
507 it->second->applications_->RemoveObserver(this);
508 background_mode_data_.erase(it);
509 // If there are no background mode profiles any longer, then turn off
510 // background mode.
511 if (!ShouldBeInBackgroundMode()) {
512 EnableLaunchOnStartup(false);
513 EndBackgroundMode();
515 UpdateStatusTrayIconContextMenu();
519 void BackgroundModeManager::OnProfileNameChanged(
520 const base::FilePath& profile_path,
521 const base::string16& old_profile_name) {
522 ProfileInfoCache& cache =
523 g_browser_process->profile_manager()->GetProfileInfoCache();
524 base::string16 new_profile_name = cache.GetNameOfProfileAtIndex(
525 cache.GetIndexOfProfileWithPath(profile_path));
526 BackgroundModeInfoMap::const_iterator it =
527 GetBackgroundModeIterator(old_profile_name);
528 // We check that the returned iterator is valid due to unittests, but really
529 // this should only be called on profiles already known by the background
530 // mode manager.
531 if (it != background_mode_data_.end()) {
532 it->second->SetName(new_profile_name);
533 UpdateStatusTrayIconContextMenu();
537 BackgroundModeManager::BackgroundModeData*
538 BackgroundModeManager::GetBackgroundModeDataForLastProfile() const {
539 Profile* most_recent_profile = g_browser_process->profile_manager()->
540 GetLastUsedProfileAllowedByPolicy();
541 BackgroundModeInfoMap::const_iterator profile_background_data =
542 background_mode_data_.find(most_recent_profile);
544 if (profile_background_data == background_mode_data_.end())
545 return NULL;
547 // Do not permit a locked profile to be used to open a browser.
548 ProfileInfoCache& cache =
549 g_browser_process->profile_manager()->GetProfileInfoCache();
550 if (cache.ProfileIsSigninRequiredAtIndex(cache.GetIndexOfProfileWithPath(
551 profile_background_data->first->GetPath())))
552 return NULL;
554 return profile_background_data->second.get();
557 ///////////////////////////////////////////////////////////////////////////////
558 // BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
559 void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) {
560 BackgroundModeData* bmd = GetBackgroundModeDataForLastProfile();
561 switch (command_id) {
562 case IDC_ABOUT:
563 if (bmd) {
564 chrome::ShowAboutChrome(bmd->GetBrowserWindow());
565 } else {
566 UserManager::Show(base::FilePath(),
567 profiles::USER_MANAGER_NO_TUTORIAL,
568 profiles::USER_MANAGER_SELECT_PROFILE_ABOUT_CHROME);
570 break;
571 case IDC_TASK_MANAGER:
572 if (bmd) {
573 chrome::OpenTaskManager(bmd->GetBrowserWindow());
574 } else {
575 UserManager::Show(base::FilePath(),
576 profiles::USER_MANAGER_NO_TUTORIAL,
577 profiles::USER_MANAGER_SELECT_PROFILE_TASK_MANAGER);
579 break;
580 case IDC_EXIT:
581 #if defined(OS_WIN)
582 browser_watcher::ExitFunnel::RecordSingleEvent(
583 chrome::kBrowserExitCodesRegistryPath, L"TraybarExit");
584 #endif
585 content::RecordAction(UserMetricsAction("Exit"));
586 chrome::CloseAllBrowsers();
587 break;
588 case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: {
589 // Background mode must already be enabled (as otherwise this menu would
590 // not be visible).
591 DCHECK(IsBackgroundModePrefEnabled());
592 DCHECK(chrome::WillKeepAlive());
594 // Set the background mode pref to "disabled" - the resulting notification
595 // will result in a call to DisableBackgroundMode().
596 PrefService* service = g_browser_process->local_state();
597 DCHECK(service);
598 service->SetBoolean(prefs::kBackgroundModeEnabled, false);
599 break;
601 default:
602 if (bmd) {
603 bmd->ExecuteCommand(command_id, event_flags);
604 } else {
605 UserManager::Show(base::FilePath(),
606 profiles::USER_MANAGER_NO_TUTORIAL,
607 profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
609 break;
614 ///////////////////////////////////////////////////////////////////////////////
615 // BackgroundModeManager, private
616 void BackgroundModeManager::DecrementKeepAliveCountForStartup() {
617 if (keep_alive_for_startup_) {
618 keep_alive_for_startup_ = false;
619 // We call this via the message queue to make sure we don't try to end
620 // keep-alive (which can shutdown Chrome) before the message loop has
621 // started.
622 base::MessageLoop::current()->PostTask(
623 FROM_HERE, base::Bind(&chrome::DecrementKeepAliveCount));
627 void BackgroundModeManager::StartBackgroundMode() {
628 DCHECK(ShouldBeInBackgroundMode());
629 // Don't bother putting ourselves in background mode if we're already there
630 // or if background mode is disabled.
631 if (in_background_mode_)
632 return;
634 // Mark ourselves as running in background mode.
635 in_background_mode_ = true;
637 UpdateKeepAliveAndTrayIcon();
639 content::NotificationService::current()->Notify(
640 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
641 content::Source<BackgroundModeManager>(this),
642 content::Details<bool>(&in_background_mode_));
645 void BackgroundModeManager::EndBackgroundMode() {
646 if (!in_background_mode_)
647 return;
648 in_background_mode_ = false;
650 UpdateKeepAliveAndTrayIcon();
652 content::NotificationService::current()->Notify(
653 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
654 content::Source<BackgroundModeManager>(this),
655 content::Details<bool>(&in_background_mode_));
658 void BackgroundModeManager::EnableBackgroundMode() {
659 DCHECK(IsBackgroundModePrefEnabled());
660 // If background mode should be enabled, but isn't, turn it on.
661 if (!in_background_mode_ && ShouldBeInBackgroundMode()) {
662 StartBackgroundMode();
663 EnableLaunchOnStartup(true);
667 void BackgroundModeManager::DisableBackgroundMode() {
668 DCHECK(!IsBackgroundModePrefEnabled());
669 // If background mode is currently enabled, turn it off.
670 if (in_background_mode_) {
671 EndBackgroundMode();
672 EnableLaunchOnStartup(false);
676 void BackgroundModeManager::SuspendBackgroundMode() {
677 background_mode_suspended_ = true;
678 UpdateKeepAliveAndTrayIcon();
681 void BackgroundModeManager::ResumeBackgroundMode() {
682 background_mode_suspended_ = false;
683 UpdateKeepAliveAndTrayIcon();
686 void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
687 if (in_background_mode_ && !background_mode_suspended_) {
688 if (!keeping_alive_) {
689 keeping_alive_ = true;
690 chrome::IncrementKeepAliveCount();
692 #if defined(OS_WIN)
693 browser_watcher::ExitFunnel::RecordSingleEvent(
694 chrome::kBrowserExitCodesRegistryPath, L"BackgroundOn");
695 #endif
697 CreateStatusTrayIcon();
698 return;
701 RemoveStatusTrayIcon();
702 if (keeping_alive_) {
703 keeping_alive_ = false;
704 chrome::DecrementKeepAliveCount();
706 #if defined(OS_WIN)
707 browser_watcher::ExitFunnel::RecordSingleEvent(
708 chrome::kBrowserExitCodesRegistryPath, L"BackgroundOff");
709 #endif
713 void BackgroundModeManager::OnBrowserAdded(Browser* browser) {
714 ResumeBackgroundMode();
717 int BackgroundModeManager::GetBackgroundAppCount() const {
718 int count = 0;
719 // Walk the BackgroundModeData for all profiles and count the number of apps.
720 for (BackgroundModeInfoMap::const_iterator it =
721 background_mode_data_.begin();
722 it != background_mode_data_.end();
723 ++it) {
724 count += it->second->GetBackgroundAppCount();
726 DCHECK(count >= 0);
727 return count;
730 int BackgroundModeManager::GetBackgroundAppCountForProfile(
731 Profile* const profile) const {
732 BackgroundModeData* bmd = GetBackgroundModeData(profile);
733 return bmd->GetBackgroundAppCount();
736 bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
737 return IsBackgroundModePrefEnabled() &&
738 (GetBackgroundAppCount() > 0 || keep_alive_for_test_);
741 void BackgroundModeManager::OnBackgroundAppInstalled(
742 const Extension* extension) {
743 // Background mode is disabled - don't do anything.
744 if (!IsBackgroundModePrefEnabled())
745 return;
747 // Ensure we have a tray icon (needed so we can display the app-installed
748 // notification below).
749 EnableBackgroundMode();
750 ResumeBackgroundMode();
752 // Notify the user that a background app has been installed.
753 if (extension) { // NULL when called by unit tests.
754 DisplayAppInstalledNotification(extension);
758 void BackgroundModeManager::CreateStatusTrayIcon() {
759 // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
760 // Chrome and Mac can use the dock icon instead.
762 // Since there are multiple profiles which share the status tray, we now
763 // use the browser process to keep track of it.
764 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
765 if (!status_tray_)
766 status_tray_ = g_browser_process->status_tray();
767 #endif
769 // If the platform doesn't support status icons, or we've already created
770 // our status icon, just return.
771 if (!status_tray_ || status_icon_)
772 return;
774 // TODO(rlp): Status tray icon should have submenus for each profile.
775 gfx::ImageSkia* image_skia = ui::ResourceBundle::GetSharedInstance().
776 GetImageSkiaNamed(IDR_STATUS_TRAY_ICON);
778 status_icon_ = status_tray_->CreateStatusIcon(
779 StatusTray::BACKGROUND_MODE_ICON,
780 *image_skia,
781 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
782 if (!status_icon_)
783 return;
784 UpdateStatusTrayIconContextMenu();
787 void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
788 // Ensure we have a tray icon if appropriate.
789 UpdateKeepAliveAndTrayIcon();
791 // If we don't have a status icon or one could not be created succesfully,
792 // then no need to continue the update.
793 if (!status_icon_)
794 return;
796 // We should only get here if we have a profile loaded, or if we're running
797 // in test mode.
798 if (background_mode_data_.empty()) {
799 DCHECK(keep_alive_for_test_);
800 return;
803 // We are building a new menu. Reset the Command IDs.
804 command_id_extension_vector_.clear();
806 // Clear the submenus too since we will be creating new ones.
807 submenus.clear();
809 // TODO(rlp): Add current profile color or indicator.
810 // Create a context menu item for Chrome.
811 scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
812 // Add About item
813 menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
814 menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
815 menu->AddSeparator(ui::NORMAL_SEPARATOR);
817 if (profile_cache_->GetNumberOfProfiles() > 1) {
818 std::vector<BackgroundModeData*> bmd_vector;
819 for (BackgroundModeInfoMap::iterator it =
820 background_mode_data_.begin();
821 it != background_mode_data_.end();
822 ++it) {
823 bmd_vector.push_back(it->second.get());
825 std::sort(bmd_vector.begin(), bmd_vector.end(),
826 &BackgroundModeData::BackgroundModeDataCompare);
827 int profiles_with_apps = 0;
828 for (std::vector<BackgroundModeData*>::const_iterator bmd_it =
829 bmd_vector.begin();
830 bmd_it != bmd_vector.end();
831 ++bmd_it) {
832 BackgroundModeData* bmd = *bmd_it;
833 // We should only display the profile in the status icon if it has at
834 // least one background app.
835 if (bmd->GetBackgroundAppCount() > 0) {
836 StatusIconMenuModel* submenu = new StatusIconMenuModel(bmd);
837 // The submenu constructor caller owns the lifetime of the submenu.
838 // The containing menu does not handle the lifetime.
839 submenus.push_back(submenu);
840 bmd->BuildProfileMenu(submenu, menu.get());
841 profiles_with_apps++;
844 // We should only be displaying the status tray icon if there is at least
845 // one profile with a background app.
846 DCHECK_GT(profiles_with_apps, 0);
847 } else {
848 // We should only have one profile in the cache if we are not
849 // using multi-profiles. If keep_alive_for_test_ is set, then we may not
850 // have any profiles in the cache.
851 DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) ||
852 keep_alive_for_test_);
853 background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), NULL);
856 menu->AddSeparator(ui::NORMAL_SEPARATOR);
857 menu->AddCheckItemWithStringId(
858 IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
859 IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
860 menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
861 true);
863 PrefService* service = g_browser_process->local_state();
864 DCHECK(service);
865 bool enabled =
866 service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled);
867 menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
868 enabled);
870 menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
872 context_menu_ = menu.get();
873 status_icon_->SetContextMenu(menu.Pass());
876 void BackgroundModeManager::RemoveStatusTrayIcon() {
877 if (status_icon_)
878 status_tray_->RemoveStatusIcon(status_icon_);
879 status_icon_ = NULL;
880 context_menu_ = NULL;
883 BackgroundModeManager::BackgroundModeData*
884 BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const {
885 DCHECK(background_mode_data_.find(profile) != background_mode_data_.end());
886 return background_mode_data_.find(profile)->second.get();
889 BackgroundModeManager::BackgroundModeInfoMap::iterator
890 BackgroundModeManager::GetBackgroundModeIterator(
891 const base::string16& profile_name) {
892 BackgroundModeInfoMap::iterator profile_it =
893 background_mode_data_.end();
894 for (BackgroundModeInfoMap::iterator it =
895 background_mode_data_.begin();
896 it != background_mode_data_.end();
897 ++it) {
898 if (it->second->name() == profile_name) {
899 profile_it = it;
902 return profile_it;
905 bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
906 PrefService* service = g_browser_process->local_state();
907 DCHECK(service);
908 return service->GetBoolean(prefs::kBackgroundModeEnabled);