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