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