background_mode_manager.cc revision 0f1bc08d4cfcc34181b0b5cbf065c40f687bf740
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
52using content::UserMetricsAction;
53using extensions::Extension;
54using extensions::UpdatedExtensionPermissionsInfo;
55
56BackgroundModeManager::BackgroundModeData::BackgroundModeData(
57    int command_id,
58    Profile* profile)
59    : applications_(new BackgroundApplicationListModel(profile)),
60      command_id_(command_id),
61      profile_(profile) {
62}
63
64BackgroundModeManager::BackgroundModeData::~BackgroundModeData() {
65}
66
67///////////////////////////////////////////////////////////////////////////////
68//  BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
69void 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
85Browser* 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
93int BackgroundModeManager::BackgroundModeData::GetBackgroundAppCount() const {
94  return applications_->size();
95}
96
97void 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
139void BackgroundModeManager::BackgroundModeData::SetName(
140    const string16& new_profile_name) {
141  name_ = new_profile_name;
142}
143
144string16 BackgroundModeManager::BackgroundModeData::name() {
145  return name_;
146}
147
148// static
149bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
150    const BackgroundModeData* bmd1,
151    const BackgroundModeData* bmd2) {
152  return bmd1->name_ < bmd2->name_;
153}
154
155
156///////////////////////////////////////////////////////////////////////////////
157//  BackgroundModeManager, public
158BackgroundModeManager::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
218BackgroundModeManager::~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
237void 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
247void 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
285void BackgroundModeManager::LaunchBackgroundApplication(
286    Profile* profile,
287    const Extension* extension) {
288  OpenApplication(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB));
289}
290
291bool BackgroundModeManager::IsBackgroundModeActive() {
292  return in_background_mode_;
293}
294
295int BackgroundModeManager::NumberOfBackgroundModeData() {
296  return background_mode_data_.size();
297}
298
299///////////////////////////////////////////////////////////////////////////////
300//  BackgroundModeManager, content::NotificationObserver overrides
301void 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
365void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
366  if (IsBackgroundModePrefEnabled())
367    EnableBackgroundMode();
368  else
369    DisableBackgroundMode();
370}
371
372///////////////////////////////////////////////////////////////////////////////
373//  BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
374void BackgroundModeManager::OnApplicationDataChanged(
375    const Extension* extension, Profile* profile) {
376  UpdateStatusTrayIconContextMenu();
377}
378
379void 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
416void 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
436void 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
452void 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
472void 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
510void 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
521void 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
539void 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
552void 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
561void 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
570void BackgroundModeManager::SuspendBackgroundMode() {
571  background_mode_suspended_ = true;
572  UpdateKeepAliveAndTrayIcon();
573}
574
575void BackgroundModeManager::ResumeBackgroundMode() {
576  background_mode_suspended_ = false;
577  UpdateKeepAliveAndTrayIcon();
578}
579
580void 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
597void BackgroundModeManager::OnBrowserAdded(Browser* browser) {
598  ResumeBackgroundMode();
599}
600
601int 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
614int BackgroundModeManager::GetBackgroundAppCountForProfile(
615    Profile* const profile) const {
616  BackgroundModeData* bmd = GetBackgroundModeData(profile);
617  return bmd->GetBackgroundAppCount();
618}
619
620bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
621  return IsBackgroundModePrefEnabled() &&
622      (GetBackgroundAppCount() > 0 || keep_alive_for_test_);
623}
624
625void 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
642void 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
658void 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
687void 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
767void BackgroundModeManager::RemoveStatusTrayIcon() {
768  if (status_icon_)
769    status_tray_->RemoveStatusIcon(status_icon_);
770  status_icon_ = NULL;
771  context_menu_ = NULL;
772}
773
774BackgroundModeManager::BackgroundModeData*
775BackgroundModeManager::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
780BackgroundModeManager::BackgroundModeInfoMap::iterator
781BackgroundModeManager::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
796bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
797  PrefService* service = g_browser_process->local_state();
798  DCHECK(service);
799  return service->GetBoolean(prefs::kBackgroundModeEnabled);
800}
801