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