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