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