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