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