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