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