12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/signin/signin_global_error.h"
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/logging.h"
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/app/chrome_command_ids.h"
97dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "chrome/browser/profiles/profile.h"
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/signin/signin_header_helper.h"
117dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "chrome/browser/signin/signin_manager_factory.h"
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "chrome/browser/signin/signin_promo.h"
132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/browser_commands.h"
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/ui/browser_window.h"
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/chrome_pages.h"
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/global_error/global_error_service.h"
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/global_error/global_error_service_factory.h"
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "chrome/browser/ui/singleton_tabs.h"
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/webui/signin/login_ui_service.h"
202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/url_constants.h"
2203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "chrome/grit/chromium_strings.h"
2303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "chrome/grit/generated_resources.h"
24e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch#include "components/signin/core/browser/signin_manager.h"
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "components/signin/core/common/profile_management_switches.h"
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "net/base/url_util.h"
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)SigninGlobalError::SigninGlobalError(
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    SigninErrorController* error_controller,
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    Profile* profile)
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    : profile_(profile),
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      error_controller_(error_controller),
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      is_added_to_global_error_service_(false) {
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  error_controller_->AddObserver(this);
36f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  is_added_to_global_error_service_ = !switches::IsNewAvatarMenu();
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (is_added_to_global_error_service_)
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    GlobalErrorServiceFactory::GetForProfile(profile_)->AddGlobalError(this);
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)SigninGlobalError::~SigninGlobalError() {
42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DCHECK(!error_controller_)
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      << "SigninGlobalError::Shutdown() was not called";
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)bool SigninGlobalError::HasError() {
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return HasMenuItem();
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void SigninGlobalError::AttemptToFixError(Browser* browser) {
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!HasError())
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ExecuteMenuItem(browser);
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)void SigninGlobalError::Shutdown() {
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (is_added_to_global_error_service_) {
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    GlobalErrorServiceFactory::GetForProfile(profile_)->RemoveGlobalError(this);
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    is_added_to_global_error_service_ = false;
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  error_controller_->RemoveObserver(this);
64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  error_controller_ = NULL;
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)bool SigninGlobalError::HasMenuItem() {
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return error_controller_->HasError();
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)int SigninGlobalError::MenuItemCommandID() {
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return IDC_SHOW_SIGNIN_ERROR;
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)base::string16 SigninGlobalError::MenuItemLabel() {
76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Notify the user if there's an auth error the user should know about.
77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (error_controller_->HasError())
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return l10n_util::GetStringUTF16(IDS_SYNC_SIGN_IN_ERROR_WRENCH_MENU_ITEM);
79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return base::string16();
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void SigninGlobalError::ExecuteMenuItem(Browser* browser) {
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#if defined(OS_CHROMEOS)
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (error_controller_->auth_error().state() !=
85a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      GoogleServiceAuthError::NONE) {
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    DVLOG(1) << "Signing out the user to fix a sync error.";
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // TODO(beng): seems like this could just call chrome::AttemptUserExit().
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    chrome::ExecuteCommand(browser, IDC_EXIT);
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return;
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#endif
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Global errors don't show up in the wrench menu on android.
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#if !defined(OS_ANDROID)
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  LoginUIService* login_ui = LoginUIServiceFactory::GetForProfile(profile_);
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (login_ui->current_login_ui()) {
972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    login_ui->current_login_ui()->FocusUI();
982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return;
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
101f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (switches::IsNewAvatarMenu()) {
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    browser->window()->ShowAvatarBubbleFromAvatarButton(
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH,
1046d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        signin::ManageAccountsParams());
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    chrome::ShowSingletonTab(
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        browser,
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        signin::GetReauthURL(profile_, error_controller_->error_account_id()));
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#endif
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)bool SigninGlobalError::HasBubbleView() {
114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return !GetBubbleViewMessages().empty();
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)base::string16 SigninGlobalError::GetBubbleViewTitle() {
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return l10n_util::GetStringUTF16(IDS_SIGNIN_ERROR_BUBBLE_VIEW_TITLE);
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)std::vector<base::string16> SigninGlobalError::GetBubbleViewMessages() {
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::vector<base::string16> messages;
1237dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // If the user isn't signed in, no need to display an error bubble.
1257dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  SigninManagerBase* signin_manager =
1267dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      SigninManagerFactory::GetForProfileIfExists(profile_);
1271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (signin_manager && !signin_manager->IsAuthenticated())
1287dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      return messages;
1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
130a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (!error_controller_->HasError())
131a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return messages;
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
133a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  switch (error_controller_->auth_error().state()) {
1344e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // TODO(rogerta): use account id in error messages.
1354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // User credentials are invalid (bad acct, etc).
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
138f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    case GoogleServiceAuthError::SERVICE_ERROR:
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    case GoogleServiceAuthError::ACCOUNT_DELETED:
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    case GoogleServiceAuthError::ACCOUNT_DISABLED:
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      messages.push_back(l10n_util::GetStringUTF16(
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_MESSAGE));
143c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      break;
1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Sync service is not available for this account's domain.
1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      messages.push_back(l10n_util::GetStringUTF16(
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          IDS_SYNC_UNAVAILABLE_ERROR_BUBBLE_VIEW_MESSAGE));
149c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      break;
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Generic message for "other" errors.
1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    default:
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      messages.push_back(l10n_util::GetStringUTF16(
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          IDS_SYNC_OTHER_SIGN_IN_ERROR_BUBBLE_VIEW_MESSAGE));
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
156c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return messages;
1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)base::string16 SigninGlobalError::GetBubbleViewAcceptButtonLabel() {
160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // If the auth service is unavailable, don't give the user the option to try
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // signing in again.
162a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (error_controller_->auth_error().state() ==
163a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return l10n_util::GetStringUTF16(
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        IDS_SYNC_UNAVAILABLE_ERROR_BUBBLE_VIEW_ACCEPT);
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  } else {
1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return l10n_util::GetStringUTF16(IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_ACCEPT);
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)base::string16 SigninGlobalError::GetBubbleViewCancelButtonLabel() {
172a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  return base::string16();
1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void SigninGlobalError::OnBubbleViewDidClose(Browser* browser) {
1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void SigninGlobalError::BubbleViewAcceptButtonPressed(Browser* browser) {
1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  ExecuteMenuItem(browser);
1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void SigninGlobalError::BubbleViewCancelButtonPressed(Browser* browser) {
1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  NOTREACHED();
1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1857dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)void SigninGlobalError::OnErrorChanged() {
187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  GlobalErrorServiceFactory::GetForProfile(profile_)->NotifyErrorsChanged(this);
1887dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch}
189