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 "content/public/browser/notification_service.h" 42 #include "content/public/browser/user_metrics.h" 43 #include "extensions/browser/extension_system.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" 51 52 using base::UserMetricsAction; 53 using extensions::Extension; 54 using extensions::UpdatedExtensionPermissionsInfo; 55 56 namespace { 57 const int kInvalidExtensionIndex = -1; 58 } 59 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) { 66 } 67 68 BackgroundModeManager::BackgroundModeData::~BackgroundModeData() { 69 } 70 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); 87 } 88 break; 89 } 90 } 91 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); 98 } 99 100 int BackgroundModeManager::BackgroundModeData::GetBackgroundAppCount() const { 101 return applications_->size(); 102 } 103 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)); 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 155 void BackgroundModeManager::BackgroundModeData::SetName( 156 const base::string16& new_profile_name) { 157 name_ = new_profile_name; 158 } 159 160 base::string16 BackgroundModeManager::BackgroundModeData::name() { 161 return name_; 162 } 163 164 // static 165 bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare( 166 const BackgroundModeData* bmd1, 167 const BackgroundModeData* bmd2) { 168 return bmd1->name_ < bmd2->name_; 169 } 170 171 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()); 190 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); 194 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))); 202 } 203 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::IncrementKeepAliveCount(); 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(); 216 } 217 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; 222 223 if (ShouldBeInBackgroundMode()) 224 StartBackgroundMode(); 225 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); 231 } 232 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); 241 } 242 BrowserList::RemoveObserver(this); 243 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(); 249 } 250 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); 259 } 260 261 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; 268 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); 275 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, 280 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, 281 content::Source<Profile>(profile)); 282 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED, 283 content::Source<Profile>(profile)); 284 285 286 // Check for the presence of background apps after all extensions have been 287 // loaded, to handle the case where an extension has been manually removed 288 // while Chrome was not running. 289 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, 290 content::Source<Profile>(profile)); 291 292 bmd->applications_->AddObserver(this); 293 294 // If we're adding a new profile and running in multi-profile mode, this new 295 // profile should be added to the status icon if one currently exists. 296 if (in_background_mode_ && status_icon_) 297 UpdateStatusTrayIconContextMenu(); 298 } 299 300 // static 301 void BackgroundModeManager::LaunchBackgroundApplication( 302 Profile* profile, 303 const Extension* extension) { 304 OpenApplication(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB)); 305 } 306 307 bool BackgroundModeManager::IsBackgroundModeActive() { 308 return in_background_mode_; 309 } 310 311 int BackgroundModeManager::NumberOfBackgroundModeData() { 312 return background_mode_data_.size(); 313 } 314 315 /////////////////////////////////////////////////////////////////////////////// 316 // BackgroundModeManager, content::NotificationObserver overrides 317 void BackgroundModeManager::Observe( 318 int type, 319 const content::NotificationSource& source, 320 const content::NotificationDetails& details) { 321 switch (type) { 322 case chrome::NOTIFICATION_EXTENSIONS_READY: 323 // Extensions are loaded, so we don't need to manually keep the browser 324 // process alive any more when running in no-startup-window mode. 325 DecrementKeepAliveCountForStartup(); 326 break; 327 328 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: { 329 Extension* extension = content::Details<Extension>(details).ptr(); 330 Profile* profile = content::Source<Profile>(source).ptr(); 331 if (BackgroundApplicationListModel::IsBackgroundApp( 332 *extension, profile)) { 333 // Extensions loaded after the ExtensionsService is ready should be 334 // treated as new installs. 335 if (extensions::ExtensionSystem::Get(profile)->extension_service()-> 336 is_ready()) { 337 bool is_being_reloaded = false; 338 CheckReloadStatus(extension, &is_being_reloaded); 339 // No need to show the notification if we showed to the user 340 // previously for this app. 341 if (!is_being_reloaded) 342 OnBackgroundAppInstalled(extension); 343 } 344 } 345 } 346 break; 347 case chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED: { 348 UpdatedExtensionPermissionsInfo* info = 349 content::Details<UpdatedExtensionPermissionsInfo>(details).ptr(); 350 if (info->permissions->HasAPIPermission( 351 extensions::APIPermission::kBackground) && 352 info->reason == UpdatedExtensionPermissionsInfo::ADDED) { 353 // Turned on background permission, so treat this as a new install. 354 OnBackgroundAppInstalled(info->extension); 355 } 356 } 357 break; 358 case chrome::NOTIFICATION_APP_TERMINATING: 359 // Make sure we aren't still keeping the app alive (only happens if we 360 // don't receive an EXTENSIONS_READY notification for some reason). 361 DecrementKeepAliveCountForStartup(); 362 // Performing an explicit shutdown, so exit background mode (does nothing 363 // if we aren't in background mode currently). 364 EndBackgroundMode(); 365 // Shutting down, so don't listen for any more notifications so we don't 366 // try to re-enter/exit background mode again. 367 registrar_.RemoveAll(); 368 for (BackgroundModeInfoMap::iterator it = 369 background_mode_data_.begin(); 370 it != background_mode_data_.end(); 371 ++it) { 372 it->second->applications_->RemoveObserver(this); 373 } 374 break; 375 default: 376 NOTREACHED(); 377 break; 378 } 379 } 380 381 void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() { 382 if (IsBackgroundModePrefEnabled()) 383 EnableBackgroundMode(); 384 else 385 DisableBackgroundMode(); 386 } 387 388 /////////////////////////////////////////////////////////////////////////////// 389 // BackgroundModeManager, BackgroundApplicationListModel::Observer overrides 390 void BackgroundModeManager::OnApplicationDataChanged( 391 const Extension* extension, Profile* profile) { 392 UpdateStatusTrayIconContextMenu(); 393 } 394 395 void BackgroundModeManager::OnApplicationListChanged(Profile* profile) { 396 if (!IsBackgroundModePrefEnabled()) 397 return; 398 399 // Update the profile cache with the fact whether background apps are running 400 // for this profile. 401 size_t profile_index = profile_cache_->GetIndexOfProfileWithPath( 402 profile->GetPath()); 403 if (profile_index != std::string::npos) { 404 profile_cache_->SetBackgroundStatusOfProfileAtIndex( 405 profile_index, GetBackgroundAppCountForProfile(profile) != 0); 406 } 407 408 if (!ShouldBeInBackgroundMode()) { 409 // We've uninstalled our last background app, make sure we exit background 410 // mode and no longer launch on startup. 411 EnableLaunchOnStartup(false); 412 EndBackgroundMode(); 413 } else { 414 // We have at least one background app running - make sure we're in 415 // background mode. 416 if (!in_background_mode_) { 417 // We're entering background mode - make sure we have launch-on-startup 418 // enabled. On Mac, the platform-specific code tracks whether the user 419 // has deleted a login item in the past, and if so, no login item will 420 // be created (to avoid overriding the specific user action). 421 EnableLaunchOnStartup(true); 422 423 StartBackgroundMode(); 424 } 425 // List of applications changed so update the UI. 426 UpdateStatusTrayIconContextMenu(); 427 } 428 } 429 430 /////////////////////////////////////////////////////////////////////////////// 431 // BackgroundModeManager, ProfileInfoCacheObserver overrides 432 void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) { 433 ProfileInfoCache& cache = 434 g_browser_process->profile_manager()->GetProfileInfoCache(); 435 base::string16 profile_name = cache.GetNameOfProfileAtIndex( 436 cache.GetIndexOfProfileWithPath(profile_path)); 437 // At this point, the profile should be registered with the background mode 438 // manager, but when it's actually added to the cache is when its name is 439 // set so we need up to update that with the background_mode_data. 440 for (BackgroundModeInfoMap::const_iterator it = 441 background_mode_data_.begin(); 442 it != background_mode_data_.end(); 443 ++it) { 444 if (it->first->GetPath() == profile_path) { 445 it->second->SetName(profile_name); 446 UpdateStatusTrayIconContextMenu(); 447 return; 448 } 449 } 450 } 451 452 void BackgroundModeManager::OnProfileWillBeRemoved( 453 const base::FilePath& profile_path) { 454 ProfileInfoCache& cache = 455 g_browser_process->profile_manager()->GetProfileInfoCache(); 456 base::string16 profile_name = cache.GetNameOfProfileAtIndex( 457 cache.GetIndexOfProfileWithPath(profile_path)); 458 // Remove the profile from our map of profiles. 459 BackgroundModeInfoMap::iterator it = 460 GetBackgroundModeIterator(profile_name); 461 // If a profile isn't running a background app, it may not be in the map. 462 if (it != background_mode_data_.end()) { 463 it->second->applications_->RemoveObserver(this); 464 background_mode_data_.erase(it); 465 // If there are no background mode profiles any longer, then turn off 466 // background mode. 467 if (!ShouldBeInBackgroundMode()) { 468 EnableLaunchOnStartup(false); 469 EndBackgroundMode(); 470 } 471 UpdateStatusTrayIconContextMenu(); 472 } 473 } 474 475 void BackgroundModeManager::OnProfileNameChanged( 476 const base::FilePath& profile_path, 477 const base::string16& old_profile_name) { 478 ProfileInfoCache& cache = 479 g_browser_process->profile_manager()->GetProfileInfoCache(); 480 base::string16 new_profile_name = cache.GetNameOfProfileAtIndex( 481 cache.GetIndexOfProfileWithPath(profile_path)); 482 BackgroundModeInfoMap::const_iterator it = 483 GetBackgroundModeIterator(old_profile_name); 484 // We check that the returned iterator is valid due to unittests, but really 485 // this should only be called on profiles already known by the background 486 // mode manager. 487 if (it != background_mode_data_.end()) { 488 it->second->SetName(new_profile_name); 489 UpdateStatusTrayIconContextMenu(); 490 } 491 } 492 493 /////////////////////////////////////////////////////////////////////////////// 494 // BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides 495 void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) { 496 // When a browser window is necessary, we use the first profile. The windows 497 // opened for these commands are not profile-specific, so any profile would 498 // work and the first is convenient. 499 BackgroundModeData* bmd = background_mode_data_.begin()->second.get(); 500 switch (command_id) { 501 case IDC_ABOUT: 502 chrome::ShowAboutChrome(bmd->GetBrowserWindow()); 503 break; 504 case IDC_TASK_MANAGER: 505 chrome::OpenTaskManager(bmd->GetBrowserWindow()); 506 break; 507 case IDC_EXIT: 508 content::RecordAction(UserMetricsAction("Exit")); 509 chrome::CloseAllBrowsers(); 510 break; 511 case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: { 512 // Background mode must already be enabled (as otherwise this menu would 513 // not be visible). 514 DCHECK(IsBackgroundModePrefEnabled()); 515 DCHECK(chrome::WillKeepAlive()); 516 517 // Set the background mode pref to "disabled" - the resulting notification 518 // will result in a call to DisableBackgroundMode(). 519 PrefService* service = g_browser_process->local_state(); 520 DCHECK(service); 521 service->SetBoolean(prefs::kBackgroundModeEnabled, false); 522 break; 523 } 524 default: 525 bmd->ExecuteCommand(command_id, event_flags); 526 break; 527 } 528 } 529 530 531 /////////////////////////////////////////////////////////////////////////////// 532 // BackgroundModeManager, private 533 void BackgroundModeManager::DecrementKeepAliveCountForStartup() { 534 if (keep_alive_for_startup_) { 535 keep_alive_for_startup_ = false; 536 // We call this via the message queue to make sure we don't try to end 537 // keep-alive (which can shutdown Chrome) before the message loop has 538 // started. 539 base::MessageLoop::current()->PostTask( 540 FROM_HERE, base::Bind(&chrome::DecrementKeepAliveCount)); 541 } 542 } 543 544 void BackgroundModeManager::StartBackgroundMode() { 545 DCHECK(ShouldBeInBackgroundMode()); 546 // Don't bother putting ourselves in background mode if we're already there 547 // or if background mode is disabled. 548 if (in_background_mode_) 549 return; 550 551 // Mark ourselves as running in background mode. 552 in_background_mode_ = true; 553 554 UpdateKeepAliveAndTrayIcon(); 555 556 content::NotificationService::current()->Notify( 557 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED, 558 content::Source<BackgroundModeManager>(this), 559 content::Details<bool>(&in_background_mode_)); 560 } 561 562 void BackgroundModeManager::EndBackgroundMode() { 563 if (!in_background_mode_) 564 return; 565 in_background_mode_ = false; 566 567 UpdateKeepAliveAndTrayIcon(); 568 569 content::NotificationService::current()->Notify( 570 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED, 571 content::Source<BackgroundModeManager>(this), 572 content::Details<bool>(&in_background_mode_)); 573 } 574 575 void BackgroundModeManager::EnableBackgroundMode() { 576 DCHECK(IsBackgroundModePrefEnabled()); 577 // If background mode should be enabled, but isn't, turn it on. 578 if (!in_background_mode_ && ShouldBeInBackgroundMode()) { 579 StartBackgroundMode(); 580 EnableLaunchOnStartup(true); 581 } 582 } 583 584 void BackgroundModeManager::DisableBackgroundMode() { 585 DCHECK(!IsBackgroundModePrefEnabled()); 586 // If background mode is currently enabled, turn it off. 587 if (in_background_mode_) { 588 EndBackgroundMode(); 589 EnableLaunchOnStartup(false); 590 } 591 } 592 593 void BackgroundModeManager::SuspendBackgroundMode() { 594 background_mode_suspended_ = true; 595 UpdateKeepAliveAndTrayIcon(); 596 } 597 598 void BackgroundModeManager::ResumeBackgroundMode() { 599 background_mode_suspended_ = false; 600 UpdateKeepAliveAndTrayIcon(); 601 } 602 603 void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() { 604 if (in_background_mode_ && !background_mode_suspended_) { 605 if (!keeping_alive_) { 606 keeping_alive_ = true; 607 chrome::IncrementKeepAliveCount(); 608 } 609 CreateStatusTrayIcon(); 610 return; 611 } 612 613 RemoveStatusTrayIcon(); 614 if (keeping_alive_) { 615 keeping_alive_ = false; 616 chrome::DecrementKeepAliveCount(); 617 } 618 } 619 620 void BackgroundModeManager::OnBrowserAdded(Browser* browser) { 621 ResumeBackgroundMode(); 622 } 623 624 int BackgroundModeManager::GetBackgroundAppCount() const { 625 int count = 0; 626 // Walk the BackgroundModeData for all profiles and count the number of apps. 627 for (BackgroundModeInfoMap::const_iterator it = 628 background_mode_data_.begin(); 629 it != background_mode_data_.end(); 630 ++it) { 631 count += it->second->GetBackgroundAppCount(); 632 } 633 DCHECK(count >= 0); 634 return count; 635 } 636 637 int BackgroundModeManager::GetBackgroundAppCountForProfile( 638 Profile* const profile) const { 639 BackgroundModeData* bmd = GetBackgroundModeData(profile); 640 return bmd->GetBackgroundAppCount(); 641 } 642 643 bool BackgroundModeManager::ShouldBeInBackgroundMode() const { 644 return IsBackgroundModePrefEnabled() && 645 (GetBackgroundAppCount() > 0 || keep_alive_for_test_); 646 } 647 648 void BackgroundModeManager::OnBackgroundAppInstalled( 649 const Extension* extension) { 650 // Background mode is disabled - don't do anything. 651 if (!IsBackgroundModePrefEnabled()) 652 return; 653 654 // Ensure we have a tray icon (needed so we can display the app-installed 655 // notification below). 656 EnableBackgroundMode(); 657 ResumeBackgroundMode(); 658 659 // Notify the user that a background app has been installed. 660 if (extension) { // NULL when called by unit tests. 661 DisplayAppInstalledNotification(extension); 662 } 663 } 664 665 void BackgroundModeManager::CheckReloadStatus( 666 const Extension* extension, 667 bool* is_being_reloaded) { 668 // Walk the BackgroundModeData for all profiles to see if one of their 669 // extensions is being reloaded. 670 for (BackgroundModeInfoMap::const_iterator it = 671 background_mode_data_.begin(); 672 it != background_mode_data_.end(); 673 ++it) { 674 Profile* profile = it->first; 675 // If the extension is being reloaded, no need to show a notification. 676 if (profile->GetExtensionService()->IsBeingReloaded(extension->id())) 677 *is_being_reloaded = true; 678 } 679 } 680 681 void BackgroundModeManager::CreateStatusTrayIcon() { 682 // Only need status icons on windows/linux. ChromeOS doesn't allow exiting 683 // Chrome and Mac can use the dock icon instead. 684 685 // Since there are multiple profiles which share the status tray, we now 686 // use the browser process to keep track of it. 687 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) 688 if (!status_tray_) 689 status_tray_ = g_browser_process->status_tray(); 690 #endif 691 692 // If the platform doesn't support status icons, or we've already created 693 // our status icon, just return. 694 if (!status_tray_ || status_icon_) 695 return; 696 697 // TODO(rlp): Status tray icon should have submenus for each profile. 698 gfx::ImageSkia* image_skia = ui::ResourceBundle::GetSharedInstance(). 699 GetImageSkiaNamed(IDR_STATUS_TRAY_ICON); 700 701 status_icon_ = status_tray_->CreateStatusIcon( 702 StatusTray::BACKGROUND_MODE_ICON, 703 *image_skia, 704 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); 705 if (!status_icon_) 706 return; 707 UpdateStatusTrayIconContextMenu(); 708 } 709 710 void BackgroundModeManager::UpdateStatusTrayIconContextMenu() { 711 // Ensure we have a tray icon if appropriate. 712 UpdateKeepAliveAndTrayIcon(); 713 714 // If we don't have a status icon or one could not be created succesfully, 715 // then no need to continue the update. 716 if (!status_icon_) 717 return; 718 719 // We should only get here if we have a profile loaded, or if we're running 720 // in test mode. 721 if (background_mode_data_.empty()) { 722 DCHECK(keep_alive_for_test_); 723 return; 724 } 725 726 // We are building a new menu. Reset the Command IDs. 727 command_id_extension_vector_.clear(); 728 729 // Clear the submenus too since we will be creating new ones. 730 submenus.clear(); 731 732 // TODO(rlp): Add current profile color or indicator. 733 // Create a context menu item for Chrome. 734 scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this)); 735 // Add About item 736 menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT)); 737 menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER); 738 menu->AddSeparator(ui::NORMAL_SEPARATOR); 739 740 if (profile_cache_->GetNumberOfProfiles() > 1) { 741 std::vector<BackgroundModeData*> bmd_vector; 742 for (BackgroundModeInfoMap::iterator it = 743 background_mode_data_.begin(); 744 it != background_mode_data_.end(); 745 ++it) { 746 bmd_vector.push_back(it->second.get()); 747 } 748 std::sort(bmd_vector.begin(), bmd_vector.end(), 749 &BackgroundModeData::BackgroundModeDataCompare); 750 int profiles_with_apps = 0; 751 for (std::vector<BackgroundModeData*>::const_iterator bmd_it = 752 bmd_vector.begin(); 753 bmd_it != bmd_vector.end(); 754 ++bmd_it) { 755 BackgroundModeData* bmd = *bmd_it; 756 // We should only display the profile in the status icon if it has at 757 // least one background app. 758 if (bmd->GetBackgroundAppCount() > 0) { 759 StatusIconMenuModel* submenu = new StatusIconMenuModel(bmd); 760 // The submenu constructor caller owns the lifetime of the submenu. 761 // The containing menu does not handle the lifetime. 762 submenus.push_back(submenu); 763 bmd->BuildProfileMenu(submenu, menu.get()); 764 profiles_with_apps++; 765 } 766 } 767 // We should only be displaying the status tray icon if there is at least 768 // one profile with a background app. 769 DCHECK_GT(profiles_with_apps, 0); 770 } else { 771 // We should only have one profile in the cache if we are not 772 // using multi-profiles. If keep_alive_for_test_ is set, then we may not 773 // have any profiles in the cache. 774 DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) || 775 keep_alive_for_test_); 776 background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), NULL); 777 } 778 779 menu->AddSeparator(ui::NORMAL_SEPARATOR); 780 menu->AddCheckItemWithStringId( 781 IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND, 782 IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND); 783 menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND, 784 true); 785 786 PrefService* service = g_browser_process->local_state(); 787 DCHECK(service); 788 bool enabled = 789 service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled); 790 menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND, 791 enabled); 792 793 menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT); 794 795 context_menu_ = menu.get(); 796 status_icon_->SetContextMenu(menu.Pass()); 797 } 798 799 void BackgroundModeManager::RemoveStatusTrayIcon() { 800 if (status_icon_) 801 status_tray_->RemoveStatusIcon(status_icon_); 802 status_icon_ = NULL; 803 context_menu_ = NULL; 804 } 805 806 BackgroundModeManager::BackgroundModeData* 807 BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const { 808 DCHECK(background_mode_data_.find(profile) != background_mode_data_.end()); 809 return background_mode_data_.find(profile)->second.get(); 810 } 811 812 BackgroundModeManager::BackgroundModeInfoMap::iterator 813 BackgroundModeManager::GetBackgroundModeIterator( 814 const base::string16& profile_name) { 815 BackgroundModeInfoMap::iterator profile_it = 816 background_mode_data_.end(); 817 for (BackgroundModeInfoMap::iterator it = 818 background_mode_data_.begin(); 819 it != background_mode_data_.end(); 820 ++it) { 821 if (it->second->name() == profile_name) { 822 profile_it = it; 823 } 824 } 825 return profile_it; 826 } 827 828 bool BackgroundModeManager::IsBackgroundModePrefEnabled() const { 829 PrefService* service = g_browser_process->local_state(); 830 DCHECK(service); 831 return service->GetBoolean(prefs::kBackgroundModeEnabled); 832 } 833