background_mode_manager.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <algorithm> 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <string> 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <vector> 8868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/base_paths.h" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/bind.h" 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/command_line.h" 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/prefs/pref_registry_simple.h" 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/prefs/pref_service.h" 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/utf_string_conversions.h" 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/app/chrome_command_ids.h" 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/background/background_application_list_model.h" 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/background/background_mode_manager.h" 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/browser_process.h" 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/browser_shutdown.h" 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/extensions/extension_service.h" 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/extensions/extension_system.h" 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/lifetime/application_lifetime.h" 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/profiles/profile.h" 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/profiles/profile_info_cache.h" 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/profiles/profile_manager.h" 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/status_icons/status_icon.h" 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/status_icons/status_tray.h" 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/browser.h" 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/browser_commands.h" 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/browser_finder.h" 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/chrome_pages.h" 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/extensions/application_launch.h" 34868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "chrome/browser/ui/host_desktop.h" 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/chrome_constants.h" 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/chrome_notification_types.h" 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/chrome_switches.h" 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/extensions/extension.h" 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/extensions/extension_constants.h" 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/extensions/permissions/permission_set.h" 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/pref_names.h" 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/notification_service.h" 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/user_metrics.h" 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/chrome_unscaled_resources.h" 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/chromium_strings.h" 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/generated_resources.h" 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/l10n/l10n_util.h" 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/resource/resource_bundle.h" 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::UserMetricsAction; 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using extensions::Extension; 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using extensions::UpdatedExtensionPermissionsInfo; 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)BackgroundModeManager::BackgroundModeData::BackgroundModeData( 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int command_id, 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Profile* profile) 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) : applications_(new BackgroundApplicationListModel(profile)), 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) command_id_(command_id), 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) profile_(profile) { 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)BackgroundModeManager::BackgroundModeData::~BackgroundModeData() { 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/////////////////////////////////////////////////////////////////////////////// 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// BackgroundModeManager::BackgroundModeData, ui::SimpleMenuModel overrides 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool BackgroundModeManager::BackgroundModeData::IsCommandIdChecked( 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int command_id) const { 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NOTREACHED() << "There are no checked items in the profile submenu."; 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool BackgroundModeManager::BackgroundModeData::IsCommandIdEnabled( 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int command_id) const { 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return command_id != IDC_MinimumLabelValue; 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool BackgroundModeManager::BackgroundModeData::GetAcceleratorForCommandId( 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int command_id, ui::Accelerator* accelerator) { 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // No accelerators for status icon context menus. 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BackgroundModeManager::BackgroundModeData::ExecuteCommand( 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int item, 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int event_flags) { 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) switch (item) { 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) case IDC_MinimumLabelValue: 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // 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 100Browser* 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 108int BackgroundModeManager::BackgroundModeData::GetBackgroundAppCount() const { 109 return applications_->size(); 110} 111 112void 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); 127 DCHECK(position == applications_->GetPosition(*cursor)); 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 138void BackgroundModeManager::BackgroundModeData::SetName( 139 const string16& new_profile_name) { 140 name_ = new_profile_name; 141} 142 143string16 BackgroundModeManager::BackgroundModeData::name() { 144 return name_; 145} 146 147// static 148bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare( 149 const BackgroundModeData* bmd1, 150 const BackgroundModeData* bmd2) { 151 return bmd1->name_ < bmd2->name_; 152} 153 154 155/////////////////////////////////////////////////////////////////////////////// 156// BackgroundModeManager, public 157BackgroundModeManager::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 209BackgroundModeManager::~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 227void 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 237void 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 275void BackgroundModeManager::LaunchBackgroundApplication( 276 Profile* profile, 277 const Extension* extension) { 278 chrome::OpenApplication(chrome::AppLaunchParams(profile, extension, 279 NEW_FOREGROUND_TAB)); 280} 281 282bool BackgroundModeManager::IsBackgroundModeActiveForTest() { 283 return in_background_mode_; 284} 285 286int BackgroundModeManager::NumberOfBackgroundModeData() { 287 return background_mode_data_.size(); 288} 289 290/////////////////////////////////////////////////////////////////////////////// 291// BackgroundModeManager, content::NotificationObserver overrides 292void 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 OnBackgroundAppInstalled(extension); 313 } 314 } 315 } 316 break; 317 case chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED: { 318 UpdatedExtensionPermissionsInfo* info = 319 content::Details<UpdatedExtensionPermissionsInfo>(details).ptr(); 320 if (info->permissions->HasAPIPermission( 321 extensions::APIPermission::kBackground) && 322 info->reason == UpdatedExtensionPermissionsInfo::ADDED) { 323 // Turned on background permission, so treat this as a new install. 324 OnBackgroundAppInstalled(info->extension); 325 } 326 } 327 break; 328 case chrome::NOTIFICATION_APP_TERMINATING: 329 // Make sure we aren't still keeping the app alive (only happens if we 330 // don't receive an EXTENSIONS_READY notification for some reason). 331 EndKeepAliveForStartup(); 332 // Performing an explicit shutdown, so exit background mode (does nothing 333 // if we aren't in background mode currently). 334 EndBackgroundMode(); 335 // Shutting down, so don't listen for any more notifications so we don't 336 // try to re-enter/exit background mode again. 337 registrar_.RemoveAll(); 338 for (BackgroundModeInfoMap::iterator it = 339 background_mode_data_.begin(); 340 it != background_mode_data_.end(); 341 ++it) { 342 it->second->applications_->RemoveObserver(this); 343 } 344 break; 345 default: 346 NOTREACHED(); 347 break; 348 } 349} 350 351void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() { 352 if (IsBackgroundModePrefEnabled()) 353 EnableBackgroundMode(); 354 else 355 DisableBackgroundMode(); 356} 357 358/////////////////////////////////////////////////////////////////////////////// 359// BackgroundModeManager, BackgroundApplicationListModel::Observer overrides 360void BackgroundModeManager::OnApplicationDataChanged( 361 const Extension* extension, Profile* profile) { 362 UpdateStatusTrayIconContextMenu(); 363} 364 365void BackgroundModeManager::OnApplicationListChanged(Profile* profile) { 366 if (!IsBackgroundModePrefEnabled()) 367 return; 368 369 // Update the profile cache with the fact whether background apps are running 370 // for this profile. 371 size_t profile_index = profile_cache_->GetIndexOfProfileWithPath( 372 profile->GetPath()); 373 if (profile_index != std::string::npos) { 374 profile_cache_->SetBackgroundStatusOfProfileAtIndex( 375 profile_index, GetBackgroundAppCountForProfile(profile) != 0); 376 } 377 378 if (!ShouldBeInBackgroundMode()) { 379 // We've uninstalled our last background app, make sure we exit background 380 // mode and no longer launch on startup. 381 EnableLaunchOnStartup(false); 382 EndBackgroundMode(); 383 } else { 384 // We have at least one background app running - make sure we're in 385 // background mode. 386 if (!in_background_mode_) { 387 // We're entering background mode - make sure we have launch-on-startup 388 // enabled. On Mac, the platform-specific code tracks whether the user 389 // has deleted a login item in the past, and if so, no login item will 390 // be created (to avoid overriding the specific user action). 391 EnableLaunchOnStartup(true); 392 393 StartBackgroundMode(); 394 } 395 // List of applications changed so update the UI. 396 UpdateStatusTrayIconContextMenu(); 397 } 398} 399 400/////////////////////////////////////////////////////////////////////////////// 401// BackgroundModeManager, ProfileInfoCacheObserver overrides 402void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) { 403 ProfileInfoCache& cache = 404 g_browser_process->profile_manager()->GetProfileInfoCache(); 405 string16 profile_name = cache.GetNameOfProfileAtIndex( 406 cache.GetIndexOfProfileWithPath(profile_path)); 407 // At this point, the profile should be registered with the background mode 408 // manager, but when it's actually added to the cache is when its name is 409 // set so we need up to update that with the background_mode_data. 410 for (BackgroundModeInfoMap::const_iterator it = 411 background_mode_data_.begin(); 412 it != background_mode_data_.end(); 413 ++it) { 414 if (it->first->GetPath() == profile_path) { 415 it->second->SetName(profile_name); 416 UpdateStatusTrayIconContextMenu(); 417 return; 418 } 419 } 420} 421 422void BackgroundModeManager::OnProfileWillBeRemoved( 423 const base::FilePath& profile_path) { 424 ProfileInfoCache& cache = 425 g_browser_process->profile_manager()->GetProfileInfoCache(); 426 string16 profile_name = cache.GetNameOfProfileAtIndex( 427 cache.GetIndexOfProfileWithPath(profile_path)); 428 // Remove the profile from our map of profiles. 429 BackgroundModeInfoMap::iterator it = 430 GetBackgroundModeIterator(profile_name); 431 // If a profile isn't running a background app, it may not be in the map. 432 if (it != background_mode_data_.end()) { 433 background_mode_data_.erase(it); 434 UpdateStatusTrayIconContextMenu(); 435 } 436} 437 438void BackgroundModeManager::OnProfileWasRemoved( 439 const base::FilePath& profile_path, 440 const string16& profile_name) { 441} 442 443void 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 461void BackgroundModeManager::OnProfileAvatarChanged( 462 const base::FilePath& profile_path) { 463 464} 465/////////////////////////////////////////////////////////////////////////////// 466// BackgroundModeManager::BackgroundModeData, ui::SimpleMenuModel overrides 467bool BackgroundModeManager::IsCommandIdChecked( 468 int command_id) const { 469 DCHECK(command_id == IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND); 470 return true; 471} 472 473bool BackgroundModeManager::IsCommandIdEnabled( 474 int command_id) const { 475 if (command_id == IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND) { 476 PrefService* service = g_browser_process->local_state(); 477 DCHECK(service); 478 return service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled); 479 } 480 return command_id != IDC_MinimumLabelValue; 481} 482 483bool BackgroundModeManager::GetAcceleratorForCommandId( 484 int command_id, ui::Accelerator* accelerator) { 485 // No accelerators for status icon context menus. 486 return false; 487} 488 489void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) { 490 // When a browser window is necessary, we use the first profile. The windows 491 // opened for these commands are not profile-specific, so any profile would 492 // work and the first is convenient. 493 BackgroundModeData* bmd = background_mode_data_.begin()->second.get(); 494 switch (command_id) { 495 case IDC_ABOUT: 496 chrome::ShowAboutChrome(bmd->GetBrowserWindow()); 497 break; 498 case IDC_TASK_MANAGER: 499 chrome::OpenTaskManager(bmd->GetBrowserWindow(), true); 500 break; 501 case IDC_EXIT: 502 content::RecordAction(UserMetricsAction("Exit")); 503 chrome::AttemptExit(); 504 break; 505 case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: { 506 // Background mode must already be enabled (as otherwise this menu would 507 // not be visible). 508 DCHECK(IsBackgroundModePrefEnabled()); 509 DCHECK(chrome::WillKeepAlive()); 510 511 // Set the background mode pref to "disabled" - the resulting notification 512 // will result in a call to DisableBackgroundMode(). 513 PrefService* service = g_browser_process->local_state(); 514 DCHECK(service); 515 service->SetBoolean(prefs::kBackgroundModeEnabled, false); 516 break; 517 } 518 default: 519 bmd->ExecuteCommand(command_id, event_flags); 520 break; 521 } 522} 523 524 525/////////////////////////////////////////////////////////////////////////////// 526// BackgroundModeManager, private 527void BackgroundModeManager::EndKeepAliveForStartup() { 528 if (keep_alive_for_startup_) { 529 keep_alive_for_startup_ = false; 530 // We call this via the message queue to make sure we don't try to end 531 // keep-alive (which can shutdown Chrome) before the message loop has 532 // started. 533 MessageLoop::current()->PostTask(FROM_HERE, 534 base::Bind(&chrome::EndKeepAlive)); 535 } 536} 537 538void BackgroundModeManager::StartBackgroundMode() { 539 DCHECK(ShouldBeInBackgroundMode()); 540 // Don't bother putting ourselves in background mode if we're already there 541 // or if background mode is disabled. 542 if (in_background_mode_) 543 return; 544 545 // Mark ourselves as running in background mode. 546 in_background_mode_ = true; 547 548 // Put ourselves in KeepAlive mode and create a status tray icon. 549 chrome::StartKeepAlive(); 550 551 // Display a status icon to exit Chrome. 552 InitStatusTrayIcon(); 553 554 content::NotificationService::current()->Notify( 555 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED, 556 content::Source<BackgroundModeManager>(this), 557 content::Details<bool>(&in_background_mode_)); 558} 559 560void BackgroundModeManager::InitStatusTrayIcon() { 561 // Only initialize status tray icons for those profiles which actually 562 // have a background app running. 563 if (ShouldBeInBackgroundMode()) 564 CreateStatusTrayIcon(); 565} 566 567void BackgroundModeManager::EndBackgroundMode() { 568 if (!in_background_mode_) 569 return; 570 in_background_mode_ = false; 571 572 // End KeepAlive mode and blow away our status tray icon. 573 chrome::EndKeepAlive(); 574 575 RemoveStatusTrayIcon(); 576 content::NotificationService::current()->Notify( 577 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED, 578 content::Source<BackgroundModeManager>(this), 579 content::Details<bool>(&in_background_mode_)); 580} 581 582void BackgroundModeManager::EnableBackgroundMode() { 583 DCHECK(IsBackgroundModePrefEnabled()); 584 // If background mode should be enabled, but isn't, turn it on. 585 if (!in_background_mode_ && ShouldBeInBackgroundMode()) { 586 StartBackgroundMode(); 587 EnableLaunchOnStartup(true); 588 } 589} 590 591void BackgroundModeManager::DisableBackgroundMode() { 592 DCHECK(!IsBackgroundModePrefEnabled()); 593 // If background mode is currently enabled, turn it off. 594 if (in_background_mode_) { 595 EndBackgroundMode(); 596 EnableLaunchOnStartup(false); 597 } 598} 599 600int BackgroundModeManager::GetBackgroundAppCount() const { 601 int count = 0; 602 // Walk the BackgroundModeData for all profiles and count the number of apps. 603 for (BackgroundModeInfoMap::const_iterator it = 604 background_mode_data_.begin(); 605 it != background_mode_data_.end(); 606 ++it) { 607 count += it->second->GetBackgroundAppCount(); 608 } 609 DCHECK(count >= 0); 610 return count; 611} 612 613int BackgroundModeManager::GetBackgroundAppCountForProfile( 614 Profile* const profile) const { 615 BackgroundModeData* bmd = GetBackgroundModeData(profile); 616 return bmd->GetBackgroundAppCount(); 617} 618 619bool BackgroundModeManager::ShouldBeInBackgroundMode() const { 620 return IsBackgroundModePrefEnabled() && 621 (GetBackgroundAppCount() > 0 || keep_alive_for_test_); 622} 623 624void BackgroundModeManager::OnBackgroundAppInstalled( 625 const Extension* extension) { 626 // Background mode is disabled - don't do anything. 627 if (!IsBackgroundModePrefEnabled()) 628 return; 629 630 // Check if we need a status tray icon and make one if we do (needed so we 631 // can display the app-installed notification below). 632 CreateStatusTrayIcon(); 633 634 // Notify the user that a background app has been installed. 635 if (extension) // NULL when called by unit tests. 636 DisplayAppInstalledNotification(extension); 637} 638 639void BackgroundModeManager::CreateStatusTrayIcon() { 640 // Only need status icons on windows/linux. ChromeOS doesn't allow exiting 641 // Chrome and Mac can use the dock icon instead. 642 643 // Since there are multiple profiles which share the status tray, we now 644 // use the browser process to keep track of it. 645#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) 646 if (!status_tray_) 647 status_tray_ = g_browser_process->status_tray(); 648#endif 649 650 // If the platform doesn't support status icons, or we've already created 651 // our status icon, just return. 652 if (!status_tray_ || status_icon_) 653 return; 654 655 status_icon_ = status_tray_->CreateStatusIcon(); 656 if (!status_icon_) 657 return; 658 659 // Set the image and add ourselves as a click observer on it. 660 // TODO(rlp): Status tray icon should have submenus for each profile. 661 gfx::ImageSkia* image_skia = ui::ResourceBundle::GetSharedInstance(). 662 GetImageSkiaNamed(IDR_STATUS_TRAY_ICON); 663 status_icon_->SetImage(*image_skia); 664 status_icon_->SetToolTip(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); 665 UpdateStatusTrayIconContextMenu(); 666} 667 668void BackgroundModeManager::UpdateStatusTrayIconContextMenu() { 669 // If no status icon exists, it's either because one wasn't created when 670 // it should have been which can happen when extensions load after the 671 // profile has already been registered with the background mode manager. 672 if (in_background_mode_ && !status_icon_) 673 CreateStatusTrayIcon(); 674 675 // If we don't have a status icon or one could not be created succesfully, 676 // then no need to continue the update. 677 if (!status_icon_) 678 return; 679 680 // We should only get here if we have a profile loaded, or if we're running 681 // in test mode. 682 if (background_mode_data_.empty()) { 683 DCHECK(keep_alive_for_test_); 684 return; 685 } 686 687 // TODO(rlp): Add current profile color or indicator. 688 // Create a context menu item for Chrome. 689 ui::SimpleMenuModel* menu = new ui::SimpleMenuModel(this); 690 // Add About item 691 menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT)); 692 menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER); 693 menu->AddSeparator(ui::NORMAL_SEPARATOR); 694 695 if (profile_cache_->GetNumberOfProfiles() > 1) { 696 std::vector<BackgroundModeData*> bmd_vector; 697 for (BackgroundModeInfoMap::iterator it = 698 background_mode_data_.begin(); 699 it != background_mode_data_.end(); 700 ++it) { 701 bmd_vector.push_back(it->second.get()); 702 } 703 std::sort(bmd_vector.begin(), bmd_vector.end(), 704 &BackgroundModeData::BackgroundModeDataCompare); 705 int profiles_with_apps = 0; 706 for (std::vector<BackgroundModeData*>::const_iterator bmd_it = 707 bmd_vector.begin(); 708 bmd_it != bmd_vector.end(); 709 ++bmd_it) { 710 BackgroundModeData* bmd = *bmd_it; 711 // We should only display the profile in the status icon if it has at 712 // least one background app. 713 if (bmd->GetBackgroundAppCount() > 0) { 714 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(bmd); 715 bmd->BuildProfileMenu(submenu, menu); 716 profiles_with_apps++; 717 } 718 } 719 // We should only be displaying the status tray icon if there is at least 720 // one profile with a background app. 721 DCHECK_GT(profiles_with_apps, 0); 722 } else { 723 // We should only have one profile in the cache if we are not 724 // using multi-profiles. If keep_alive_for_test_ is set, then we may not 725 // have any profiles in the cache. 726 DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) || 727 keep_alive_for_test_); 728 background_mode_data_.begin()->second->BuildProfileMenu(menu, NULL); 729 } 730 731 menu->AddSeparator(ui::NORMAL_SEPARATOR); 732 menu->AddCheckItemWithStringId( 733 IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND, 734 IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND); 735 menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT); 736 737 context_menu_ = menu; 738 status_icon_->SetContextMenu(menu); 739} 740 741void BackgroundModeManager::RemoveStatusTrayIcon() { 742 if (status_icon_) 743 status_tray_->RemoveStatusIcon(status_icon_); 744 status_icon_ = NULL; 745 context_menu_ = NULL; 746} 747 748BackgroundModeManager::BackgroundModeData* 749BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const { 750 DCHECK(background_mode_data_.find(profile) != background_mode_data_.end()); 751 return background_mode_data_.find(profile)->second.get(); 752} 753 754BackgroundModeManager::BackgroundModeInfoMap::iterator 755BackgroundModeManager::GetBackgroundModeIterator( 756 const string16& profile_name) { 757 BackgroundModeInfoMap::iterator profile_it = 758 background_mode_data_.end(); 759 for (BackgroundModeInfoMap::iterator it = 760 background_mode_data_.begin(); 761 it != background_mode_data_.end(); 762 ++it) { 763 if (it->second->name() == profile_name) { 764 profile_it = it; 765 } 766 } 767 return profile_it; 768} 769 770bool BackgroundModeManager::IsBackgroundModePrefEnabled() const { 771 PrefService* service = g_browser_process->local_state(); 772 DCHECK(service); 773 return service->GetBoolean(prefs::kBackgroundModeEnabled); 774} 775