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