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