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 "chrome/browser/ui/app_list/app_list_view_delegate.h"
6
7#include <vector>
8
9#include "apps/custom_launcher_page_contents.h"
10#include "base/callback.h"
11#include "base/command_line.h"
12#include "base/files/file_path.h"
13#include "base/metrics/user_metrics.h"
14#include "base/stl_util.h"
15#include "chrome/browser/apps/scoped_keep_alive.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/chrome_notification_types.h"
18#include "chrome/browser/profiles/profile_info_cache.h"
19#include "chrome/browser/profiles/profile_manager.h"
20#include "chrome/browser/search/hotword_service.h"
21#include "chrome/browser/search/hotword_service_factory.h"
22#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
23#include "chrome/browser/ui/app_list/app_list_service.h"
24#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
25#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
26#include "chrome/browser/ui/app_list/search/search_controller.h"
27#include "chrome/browser/ui/app_list/start_page_service.h"
28#include "chrome/browser/ui/apps/chrome_app_delegate.h"
29#include "chrome/browser/ui/browser_finder.h"
30#include "chrome/browser/ui/chrome_pages.h"
31#include "chrome/browser/ui/host_desktop.h"
32#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
33#include "chrome/browser/web_applications/web_app.h"
34#include "chrome/common/chrome_switches.h"
35#include "chrome/common/extensions/extension_constants.h"
36#include "chrome/common/url_constants.h"
37#include "components/signin/core/browser/signin_manager.h"
38#include "content/public/browser/browser_thread.h"
39#include "content/public/browser/notification_service.h"
40#include "content/public/browser/page_navigator.h"
41#include "content/public/browser/user_metrics.h"
42#include "content/public/browser/web_contents.h"
43#include "extensions/browser/extension_registry.h"
44#include "extensions/common/constants.h"
45#include "extensions/common/extension_set.h"
46#include "extensions/common/manifest_constants.h"
47#include "extensions/common/manifest_handlers/launcher_page_info.h"
48#include "grit/theme_resources.h"
49#include "ui/app_list/app_list_switches.h"
50#include "ui/app_list/app_list_view_delegate_observer.h"
51#include "ui/app_list/search_box_model.h"
52#include "ui/app_list/speech_ui_model.h"
53#include "ui/base/resource/resource_bundle.h"
54#include "ui/views/controls/webview/webview.h"
55
56#if defined(TOOLKIT_VIEWS)
57#include "ui/views/controls/webview/webview.h"
58#endif
59
60#if defined(USE_AURA)
61#include "ui/keyboard/keyboard_util.h"
62#endif
63
64#if defined(USE_ASH)
65#include "ash/shell.h"
66#include "ash/wm/maximize_mode/maximize_mode_controller.h"
67#include "chrome/browser/ui/ash/app_list/app_sync_ui_state_watcher.h"
68#endif
69
70#if defined(OS_WIN)
71#include "chrome/browser/web_applications/web_app_win.h"
72#endif
73
74
75namespace chrome {
76const char kAppLauncherCategoryTag[] = "AppLauncher";
77}  // namespace chrome
78
79namespace {
80
81const int kAutoLaunchDefaultTimeoutMilliSec = 50;
82
83#if defined(OS_WIN)
84void CreateShortcutInWebAppDir(
85    const base::FilePath& app_data_dir,
86    base::Callback<void(const base::FilePath&)> callback,
87    const web_app::ShortcutInfo& info) {
88  content::BrowserThread::PostTaskAndReplyWithResult(
89      content::BrowserThread::FILE,
90      FROM_HERE,
91      base::Bind(web_app::CreateShortcutInWebAppDir, app_data_dir, info),
92      callback);
93}
94#endif
95
96void PopulateUsers(const ProfileInfoCache& profile_info,
97                   const base::FilePath& active_profile_path,
98                   app_list::AppListViewDelegate::Users* users) {
99  users->clear();
100  const size_t count = profile_info.GetNumberOfProfiles();
101  for (size_t i = 0; i < count; ++i) {
102    app_list::AppListViewDelegate::User user;
103    user.name = profile_info.GetNameOfProfileAtIndex(i);
104    user.email = profile_info.GetUserNameOfProfileAtIndex(i);
105    user.profile_path = profile_info.GetPathOfProfileAtIndex(i);
106    user.active = active_profile_path == user.profile_path;
107    users->push_back(user);
108  }
109}
110
111// Gets a list of URLs of the custom launcher pages to show in the launcher.
112// Returns a URL for each installed launcher page. If --custom-launcher-page is
113// specified and valid, also includes that URL.
114void GetCustomLauncherPageUrls(content::BrowserContext* browser_context,
115                               std::vector<GURL>* urls) {
116  // First, check the command line.
117  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
118  if (app_list::switches::IsExperimentalAppListEnabled() &&
119      command_line->HasSwitch(switches::kCustomLauncherPage)) {
120    GURL custom_launcher_page_url(
121        command_line->GetSwitchValueASCII(switches::kCustomLauncherPage));
122
123    if (custom_launcher_page_url.SchemeIs(extensions::kExtensionScheme)) {
124      urls->push_back(custom_launcher_page_url);
125    } else {
126      LOG(ERROR) << "Invalid custom launcher page URL: "
127                 << custom_launcher_page_url.possibly_invalid_spec();
128    }
129  }
130
131  // Search the list of installed extensions for ones with 'launcher_page'.
132  extensions::ExtensionRegistry* extension_registry =
133      extensions::ExtensionRegistry::Get(browser_context);
134  const extensions::ExtensionSet& enabled_extensions =
135      extension_registry->enabled_extensions();
136  for (extensions::ExtensionSet::const_iterator it = enabled_extensions.begin();
137       it != enabled_extensions.end();
138       ++it) {
139    const extensions::Extension* extension = it->get();
140    extensions::LauncherPageInfo* info =
141        extensions::LauncherPageHandler::GetInfo(extension);
142    if (!info)
143      continue;
144
145    urls->push_back(extension->GetResourceURL(info->page));
146  }
147}
148
149}  // namespace
150
151AppListViewDelegate::AppListViewDelegate(AppListControllerDelegate* controller)
152    : controller_(controller),
153      profile_(NULL),
154      model_(NULL),
155      scoped_observer_(this) {
156  CHECK(controller_);
157  // The SigninManagerFactor and the SigninManagers are observed to keep the
158  // profile switcher menu up to date, with the correct list of profiles and the
159  // correct email address (or none for signed out users) for each.
160  SigninManagerFactory::GetInstance()->AddObserver(this);
161
162  // Start observing all already-created SigninManagers.
163  ProfileManager* profile_manager = g_browser_process->profile_manager();
164  std::vector<Profile*> profiles = profile_manager->GetLoadedProfiles();
165  for (std::vector<Profile*>::iterator i = profiles.begin();
166       i != profiles.end();
167       ++i) {
168    SigninManagerBase* manager =
169        SigninManagerFactory::GetForProfileIfExists(*i);
170    if (manager) {
171      DCHECK(!scoped_observer_.IsObserving(manager));
172      scoped_observer_.Add(manager);
173    }
174  }
175
176  profile_manager->GetProfileInfoCache().AddObserver(this);
177  speech_ui_.reset(new app_list::SpeechUIModel);
178
179#if defined(GOOGLE_CHROME_BUILD)
180  speech_ui_->set_logo(
181      *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
182          IDR_APP_LIST_GOOGLE_LOGO_VOICE_SEARCH));
183#endif
184
185  registrar_.Add(this,
186                 chrome::NOTIFICATION_APP_TERMINATING,
187                 content::NotificationService::AllSources());
188}
189
190AppListViewDelegate::~AppListViewDelegate() {
191  // Note that the destructor is not always called. E.g. on Mac, this is owned
192  // by a leaky singleton. Essential shutdown work must be done by observing
193  // chrome::NOTIFICATION_APP_TERMINATING.
194  SetProfile(NULL);
195  g_browser_process->profile_manager()->GetProfileInfoCache().RemoveObserver(
196      this);
197
198  SigninManagerFactory* factory = SigninManagerFactory::GetInstance();
199  if (factory)
200    factory->RemoveObserver(this);
201}
202
203void AppListViewDelegate::SetProfile(Profile* new_profile) {
204  if (profile_ == new_profile)
205    return;
206
207  if (profile_) {
208    // Note: |search_controller_| has a reference to |speech_ui_| so must be
209    // destroyed first.
210    search_controller_.reset();
211    custom_page_contents_.clear();
212    app_list::StartPageService* start_page_service =
213        app_list::StartPageService::Get(profile_);
214    if (start_page_service)
215      start_page_service->RemoveObserver(this);
216#if defined(USE_ASH)
217    app_sync_ui_state_watcher_.reset();
218#endif
219    model_ = NULL;
220  }
221
222  profile_ = new_profile;
223  if (!profile_) {
224    speech_ui_->SetSpeechRecognitionState(app_list::SPEECH_RECOGNITION_OFF);
225    return;
226  }
227
228  model_ =
229      app_list::AppListSyncableServiceFactory::GetForProfile(profile_)->model();
230
231#if defined(USE_ASH)
232  app_sync_ui_state_watcher_.reset(new AppSyncUIStateWatcher(profile_, model_));
233#endif
234
235  SetUpSearchUI();
236  SetUpProfileSwitcher();
237  SetUpCustomLauncherPages();
238
239  // Clear search query.
240  model_->search_box()->SetText(base::string16());
241}
242
243void AppListViewDelegate::SetUpSearchUI() {
244  app_list::StartPageService* start_page_service =
245      app_list::StartPageService::Get(profile_);
246  if (start_page_service)
247    start_page_service->AddObserver(this);
248
249  speech_ui_->SetSpeechRecognitionState(start_page_service
250                                            ? start_page_service->state()
251                                            : app_list::SPEECH_RECOGNITION_OFF);
252
253  search_controller_.reset(new app_list::SearchController(profile_,
254                                                          model_->search_box(),
255                                                          model_->results(),
256                                                          speech_ui_.get(),
257                                                          controller_));
258}
259
260void AppListViewDelegate::SetUpProfileSwitcher() {
261  // If a profile change is observed when there is no app list, there is nothing
262  // to update until SetProfile() calls this function again.
263  if (!profile_)
264    return;
265
266  // Don't populate the app list users if we are on the ash desktop.
267  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
268      controller_->GetAppListWindow());
269  if (desktop == chrome::HOST_DESKTOP_TYPE_ASH)
270    return;
271
272  // Populate the app list users.
273  PopulateUsers(g_browser_process->profile_manager()->GetProfileInfoCache(),
274                profile_->GetPath(),
275                &users_);
276
277  FOR_EACH_OBSERVER(
278      app_list::AppListViewDelegateObserver, observers_, OnProfilesChanged());
279}
280
281void AppListViewDelegate::SetUpCustomLauncherPages() {
282  std::vector<GURL> custom_launcher_page_urls;
283  GetCustomLauncherPageUrls(profile_, &custom_launcher_page_urls);
284  for (std::vector<GURL>::const_iterator it = custom_launcher_page_urls.begin();
285       it != custom_launcher_page_urls.end();
286       ++it) {
287    std::string extension_id = it->host();
288    apps::CustomLauncherPageContents* page_contents =
289        new apps::CustomLauncherPageContents(
290            scoped_ptr<extensions::AppDelegate>(
291                new ChromeAppDelegate(scoped_ptr<ScopedKeepAlive>())),
292            extension_id);
293    page_contents->Initialize(profile_, *it);
294    custom_page_contents_.push_back(page_contents);
295  }
296}
297
298void AppListViewDelegate::OnHotwordStateChanged(bool started) {
299  if (started) {
300    if (speech_ui_->state() == app_list::SPEECH_RECOGNITION_READY) {
301      OnSpeechRecognitionStateChanged(
302          app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING);
303    }
304  } else {
305    if (speech_ui_->state() == app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING)
306      OnSpeechRecognitionStateChanged(app_list::SPEECH_RECOGNITION_READY);
307  }
308}
309
310void AppListViewDelegate::OnHotwordRecognized() {
311  DCHECK_EQ(app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING,
312            speech_ui_->state());
313  ToggleSpeechRecognition();
314}
315
316void AppListViewDelegate::SigninManagerCreated(SigninManagerBase* manager) {
317  scoped_observer_.Add(manager);
318}
319
320void AppListViewDelegate::SigninManagerShutdown(SigninManagerBase* manager) {
321  if (scoped_observer_.IsObserving(manager))
322    scoped_observer_.Remove(manager);
323}
324
325void AppListViewDelegate::GoogleSigninFailed(
326    const GoogleServiceAuthError& error) {
327  SetUpProfileSwitcher();
328}
329
330void AppListViewDelegate::GoogleSigninSucceeded(const std::string& account_id,
331                                                const std::string& username,
332                                                const std::string& password) {
333  SetUpProfileSwitcher();
334}
335
336void AppListViewDelegate::GoogleSignedOut(const std::string& account_id,
337                                          const std::string& username) {
338  SetUpProfileSwitcher();
339}
340
341void AppListViewDelegate::OnProfileAdded(const base::FilePath& profile_path) {
342  SetUpProfileSwitcher();
343}
344
345void AppListViewDelegate::OnProfileWasRemoved(
346    const base::FilePath& profile_path,
347    const base::string16& profile_name) {
348  SetUpProfileSwitcher();
349}
350
351void AppListViewDelegate::OnProfileNameChanged(
352    const base::FilePath& profile_path,
353    const base::string16& old_profile_name) {
354  SetUpProfileSwitcher();
355}
356
357bool AppListViewDelegate::ForceNativeDesktop() const {
358  return controller_->ForceNativeDesktop();
359}
360
361void AppListViewDelegate::SetProfileByPath(const base::FilePath& profile_path) {
362  DCHECK(model_);
363  // The profile must be loaded before this is called.
364  SetProfile(
365      g_browser_process->profile_manager()->GetProfileByPath(profile_path));
366}
367
368app_list::AppListModel* AppListViewDelegate::GetModel() {
369  return model_;
370}
371
372app_list::SpeechUIModel* AppListViewDelegate::GetSpeechUI() {
373  return speech_ui_.get();
374}
375
376void AppListViewDelegate::GetShortcutPathForApp(
377    const std::string& app_id,
378    const base::Callback<void(const base::FilePath&)>& callback) {
379#if defined(OS_WIN)
380  const extensions::Extension* extension =
381      extensions::ExtensionRegistry::Get(profile_)->GetExtensionById(
382          app_id, extensions::ExtensionRegistry::EVERYTHING);
383  if (!extension) {
384    callback.Run(base::FilePath());
385    return;
386  }
387
388  base::FilePath app_data_dir(
389      web_app::GetWebAppDataDirectory(profile_->GetPath(),
390                                      extension->id(),
391                                      GURL()));
392
393  web_app::GetShortcutInfoForApp(
394      extension,
395      profile_,
396      base::Bind(CreateShortcutInWebAppDir, app_data_dir, callback));
397#else
398  callback.Run(base::FilePath());
399#endif
400}
401
402void AppListViewDelegate::StartSearch() {
403  if (search_controller_)
404    search_controller_->Start();
405}
406
407void AppListViewDelegate::StopSearch() {
408  if (search_controller_)
409    search_controller_->Stop();
410}
411
412void AppListViewDelegate::OpenSearchResult(
413    app_list::SearchResult* result,
414    bool auto_launch,
415    int event_flags) {
416  if (auto_launch)
417    base::RecordAction(base::UserMetricsAction("AppList_AutoLaunched"));
418  search_controller_->OpenResult(result, event_flags);
419}
420
421void AppListViewDelegate::InvokeSearchResultAction(
422    app_list::SearchResult* result,
423    int action_index,
424    int event_flags) {
425  search_controller_->InvokeResultAction(result, action_index, event_flags);
426}
427
428base::TimeDelta AppListViewDelegate::GetAutoLaunchTimeout() {
429  return auto_launch_timeout_;
430}
431
432void AppListViewDelegate::AutoLaunchCanceled() {
433  base::RecordAction(base::UserMetricsAction("AppList_AutoLaunchCanceled"));
434  auto_launch_timeout_ = base::TimeDelta();
435}
436
437void AppListViewDelegate::ViewInitialized() {
438  app_list::StartPageService* service =
439      app_list::StartPageService::Get(profile_);
440  if (service) {
441    service->AppListShown();
442    if (service->HotwordEnabled()) {
443      HotwordService* hotword_service =
444          HotwordServiceFactory::GetForProfile(profile_);
445      if (hotword_service)
446        hotword_service->RequestHotwordSession(this);
447    }
448  }
449}
450
451void AppListViewDelegate::Dismiss()  {
452  controller_->DismissView();
453}
454
455void AppListViewDelegate::ViewClosing() {
456  controller_->ViewClosing();
457
458  if (!profile_)
459    return;
460
461  app_list::StartPageService* service =
462      app_list::StartPageService::Get(profile_);
463  if (service) {
464    service->AppListHidden();
465    if (service->HotwordEnabled()) {
466      HotwordService* hotword_service =
467          HotwordServiceFactory::GetForProfile(profile_);
468      if (hotword_service)
469        hotword_service->StopHotwordSession(this);
470    }
471  }
472}
473
474gfx::ImageSkia AppListViewDelegate::GetWindowIcon() {
475  return controller_->GetWindowIcon();
476}
477
478void AppListViewDelegate::OpenSettings() {
479  const extensions::Extension* extension =
480      extensions::ExtensionRegistry::Get(profile_)->GetExtensionById(
481          extension_misc::kSettingsAppId,
482          extensions::ExtensionRegistry::EVERYTHING);
483  DCHECK(extension);
484  controller_->ActivateApp(profile_,
485                           extension,
486                           AppListControllerDelegate::LAUNCH_FROM_UNKNOWN,
487                           0);
488}
489
490void AppListViewDelegate::OpenHelp() {
491  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
492      controller_->GetAppListWindow());
493  chrome::ScopedTabbedBrowserDisplayer displayer(profile_, desktop);
494  content::OpenURLParams params(GURL(chrome::kAppLauncherHelpURL),
495                                content::Referrer(),
496                                NEW_FOREGROUND_TAB,
497                                ui::PAGE_TRANSITION_LINK,
498                                false);
499  displayer.browser()->OpenURL(params);
500}
501
502void AppListViewDelegate::OpenFeedback() {
503  chrome::HostDesktopType desktop = chrome::GetHostDesktopTypeForNativeWindow(
504      controller_->GetAppListWindow());
505  Browser* browser = chrome::FindTabbedBrowser(profile_, false, desktop);
506  chrome::ShowFeedbackPage(browser, std::string(),
507                           chrome::kAppLauncherCategoryTag);
508}
509
510void AppListViewDelegate::ToggleSpeechRecognition() {
511  app_list::StartPageService* service =
512      app_list::StartPageService::Get(profile_);
513  if (service)
514    service->ToggleSpeechRecognition();
515}
516
517void AppListViewDelegate::ShowForProfileByPath(
518    const base::FilePath& profile_path) {
519  controller_->ShowForProfileByPath(profile_path);
520}
521
522void AppListViewDelegate::OnSpeechResult(const base::string16& result,
523                                         bool is_final) {
524  speech_ui_->SetSpeechResult(result, is_final);
525  if (is_final) {
526    auto_launch_timeout_ = base::TimeDelta::FromMilliseconds(
527        kAutoLaunchDefaultTimeoutMilliSec);
528    model_->search_box()->SetText(result);
529  }
530}
531
532void AppListViewDelegate::OnSpeechSoundLevelChanged(int16 level) {
533  speech_ui_->UpdateSoundLevel(level);
534}
535
536void AppListViewDelegate::OnSpeechRecognitionStateChanged(
537    app_list::SpeechRecognitionState new_state) {
538  speech_ui_->SetSpeechRecognitionState(new_state);
539
540  app_list::StartPageService* service =
541      app_list::StartPageService::Get(profile_);
542  // With the new hotword extension, we need to re-request hotwording after
543  // speech recognition has stopped.
544  if (new_state == app_list::SPEECH_RECOGNITION_READY &&
545      HotwordService::IsExperimentalHotwordingEnabled() &&
546      service && service->HotwordEnabled()) {
547    HotwordService* hotword_service =
548        HotwordServiceFactory::GetForProfile(profile_);
549    if (hotword_service) {
550      hotword_service->RequestHotwordSession(this);
551    }
552  }
553}
554
555#if defined(TOOLKIT_VIEWS)
556views::View* AppListViewDelegate::CreateStartPageWebView(
557    const gfx::Size& size) {
558  app_list::StartPageService* service =
559      app_list::StartPageService::Get(profile_);
560  if (!service)
561    return NULL;
562
563  content::WebContents* web_contents = service->GetStartPageContents();
564  if (!web_contents)
565    return NULL;
566
567  DCHECK_EQ(profile_, web_contents->GetBrowserContext());
568  views::WebView* web_view = new views::WebView(
569      web_contents->GetBrowserContext());
570  web_view->SetPreferredSize(size);
571  web_view->SetWebContents(web_contents);
572  return web_view;
573}
574
575std::vector<views::View*> AppListViewDelegate::CreateCustomPageWebViews(
576    const gfx::Size& size) {
577  std::vector<views::View*> web_views;
578
579  for (ScopedVector<apps::CustomLauncherPageContents>::const_iterator it =
580           custom_page_contents_.begin();
581       it != custom_page_contents_.end();
582       ++it) {
583    content::WebContents* web_contents = (*it)->web_contents();
584    // TODO(mgiuca): DCHECK_EQ(profile_, web_contents->GetBrowserContext())
585    // after http://crbug.com/392763 resolved.
586    views::WebView* web_view =
587        new views::WebView(web_contents->GetBrowserContext());
588    web_view->SetPreferredSize(size);
589    web_view->SetWebContents(web_contents);
590    web_views.push_back(web_view);
591  }
592
593  return web_views;
594}
595#endif
596
597bool AppListViewDelegate::IsSpeechRecognitionEnabled() {
598  app_list::StartPageService* service =
599      app_list::StartPageService::Get(profile_);
600  return service && service->GetSpeechRecognitionContents();
601}
602
603const app_list::AppListViewDelegate::Users&
604AppListViewDelegate::GetUsers() const {
605  return users_;
606}
607
608bool AppListViewDelegate::ShouldCenterWindow() const {
609  if (app_list::switches::IsCenteredAppListEnabled())
610    return true;
611
612  // keyboard depends upon Aura.
613#if defined(USE_AURA)
614  // If the virtual keyboard is enabled, use the new app list position. The old
615  // position is too tall, and doesn't fit in the left-over screen space.
616  if (keyboard::IsKeyboardEnabled())
617    return true;
618#endif
619
620#if defined(USE_ASH)
621  // If it is at all possible to enter maximize mode in this configuration
622  // (which has a virtual keyboard), we should use the experimental position.
623  // This avoids having the app list change shape and position as the user
624  // enters and exits maximize mode.
625  if (ash::Shell::HasInstance() &&
626      ash::Shell::GetInstance()
627          ->maximize_mode_controller()
628          ->CanEnterMaximizeMode()) {
629    return true;
630  }
631#endif
632
633  return false;
634}
635
636void AppListViewDelegate::AddObserver(
637    app_list::AppListViewDelegateObserver* observer) {
638  observers_.AddObserver(observer);
639}
640
641void AppListViewDelegate::RemoveObserver(
642    app_list::AppListViewDelegateObserver* observer) {
643  observers_.RemoveObserver(observer);
644}
645
646void AppListViewDelegate::Observe(int type,
647                                  const content::NotificationSource& source,
648                                  const content::NotificationDetails& details) {
649  switch (type) {
650    case chrome::NOTIFICATION_APP_TERMINATING:
651      FOR_EACH_OBSERVER(
652          app_list::AppListViewDelegateObserver, observers_, OnShutdown());
653
654      SetProfile(NULL);  // Ensures launcher page web contents are torn down.
655
656      // SigninManagerFactory is not a leaky singleton (unlike this class), and
657      // its destructor will check that it has no remaining observers.
658      scoped_observer_.RemoveAll();
659      SigninManagerFactory::GetInstance()->RemoveObserver(this);
660      break;
661    default:
662      NOTREACHED();
663  }
664}
665