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