// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/policy/device_local_account.h"

#include <stddef.h>

#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "ash/common/system/chromeos/session/logout_confirmation_controller.h"
#include "ash/common/system/chromeos/session/logout_confirmation_dialog.h"
#include "ash/common/wm_shell.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
#include "chrome/browser/chromeos/extensions/external_cache.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/login/existing_user_controller.h"
#include "chrome/browser/chromeos/login/screens/base_screen.h"
#include "chrome/browser/chromeos/login/session/user_session_manager.h"
#include "chrome/browser/chromeos/login/session/user_session_manager_test_api.h"
#include "chrome/browser/chromeos/login/signin_specifics.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/ui/webui_login_view.h"
#include "chrome/browser/chromeos/login/users/avatar/user_image_manager.h"
#include "chrome/browser/chromeos/login/users/avatar/user_image_manager_impl.h"
#include "chrome/browser/chromeos/login/users/avatar/user_image_manager_test_util.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager_impl.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/cloud_external_data_manager_base_test_util.h"
#include "chrome/browser/chromeos/policy/device_local_account_policy_service.h"
#include "chrome/browser/chromeos/policy/device_policy_builder.h"
#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
#include "chrome/browser/chromeos/policy/proto/chrome_device_policy.pb.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/updater/chromeos_extension_cache_delegate.h"
#include "chrome/browser/extensions/updater/extension_cache_impl.h"
#include "chrome/browser/extensions/updater/local_extension_cache.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/policy/test/local_policy_test_server.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/extensions/app_launch_params.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
#include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/chromeos_paths.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/dbus/fake_session_manager_client.h"
#include "chromeos/login/auth/mock_auth_status_consumer.h"
#include "chromeos/login/auth/user_context.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/cloud_policy_core.h"
#include "components/policy/core/common/cloud/cloud_policy_store.h"
#include "components/policy/core/common/cloud/policy_builder.h"
#include "components/policy/core/common/external_data_fetcher.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/core/common/policy_switches.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_type.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "crypto/rsa_private_key.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/install/crx_install_error.h"
#include "extensions/browser/management_policy.h"
#include "extensions/browser/notification_types.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_status.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/icu/source/common/unicode/locid.h"
#include "ui/base/ime/chromeos/extension_ime_util.h"
#include "ui/base/ime/chromeos/input_method_descriptor.h"
#include "ui/base/ime/chromeos/input_method_manager.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"

namespace em = enterprise_management;

using chromeos::LoginScreenContext;
using testing::InvokeWithoutArgs;
using testing::Return;
using testing::_;

namespace policy {

namespace {

const char kDomain[] = "example.com";
const char kAccountId1[] = "dla1@example.com";
const char kAccountId2[] = "dla2@example.com";
const char kDisplayName1[] = "display name 1";
const char kDisplayName2[] = "display name 2";
const char* const kStartupURLs[] = {
  "chrome://policy",
  "chrome://about",
};
const char kExistentTermsOfServicePath[] = "chromeos/enterprise/tos.txt";
const char kNonexistentTermsOfServicePath[] = "chromeos/enterprise/tos404.txt";
const char kRelativeUpdateURL[] = "/service/update2/crx";
const char kUpdateManifestHeader[] =
    "<?xml version='1.0' encoding='UTF-8'?>\n"
    "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>\n";
const char kUpdateManifestTemplate[] =
    "  <app appid='%s'>\n"
    "    <updatecheck codebase='%s' version='%s' />\n"
    "  </app>\n";
const char kUpdateManifestFooter[] =
    "</gupdate>\n";
const char kHostedAppID[] = "kbmnembihfiondgfjekmnmcbddelicoi";
const char kHostedAppCRXPath[] = "extensions/hosted_app.crx";
const char kHostedAppVersion[] = "1.0.0.0";
const char kGoodExtensionID[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
const char kGoodExtensionCRXPath[] = "extensions/good.crx";
const char kGoodExtensionVersion[] = "1.0";
const char kPackagedAppCRXPath[] = "extensions/platform_apps/app_window_2.crx";
const char kShowManagedStorageID[] = "ongnjlefhnoajpbodoldndkbkdgfomlp";
const char kShowManagedStorageCRXPath[] = "extensions/show_managed_storage.crx";
const char kShowManagedStorageVersion[] = "1.0";

const char kExternalData[] = "External data";
const char kExternalDataURL[] = "http://localhost/external_data";

const char* const kSingleRecommendedLocale[] = {
  "el",
};
const char* const kRecommendedLocales1[] = {
  "pl",
  "et",
  "en-US",
};
const char* const kRecommendedLocales2[] = {
  "fr",
  "nl",
};
const char* const kInvalidRecommendedLocale[] = {
  "xx",
};
const char kPublicSessionLocale[] = "de";
const char kPublicSessionInputMethodIDTemplate[] = "_comp_ime_%sxkb:de:neo:ger";

// The sequence token used by GetKeyboardLayoutsForLocale() for its background
// tasks.
const char kSequenceToken[] = "chromeos_login_l10n_util";

// Helper that serves extension update manifests to Chrome.
class TestingUpdateManifestProvider
    : public base::RefCountedThreadSafe<TestingUpdateManifestProvider> {
 public:
  // Update manifests will be served at |relative_update_url|.
  explicit TestingUpdateManifestProvider(
      const std::string& relative_update_url);

  // When an update manifest is requested for the given extension |id|, indicate
  // that |version| of the extension can be downloaded at |crx_url|.
  void AddUpdate(const std::string& id,
                 const std::string& version,
                 const GURL& crx_url);

  // This method must be registered with the test's EmbeddedTestServer to start
  // serving update manifests.
  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
      const net::test_server::HttpRequest& request);

 private:
  struct Update {
   public:
    Update(const std::string& version, const GURL& crx_url);
    Update();

    std::string version;
    GURL crx_url;
  };
  typedef std::map<std::string, Update> UpdateMap;

  ~TestingUpdateManifestProvider();
  friend class RefCountedThreadSafe<TestingUpdateManifestProvider>;

  // Protects other members against concurrent access from main thread and
  // test server io thread.
  base::Lock lock_;

  std::string relative_update_url_;
  UpdateMap updates_;

  DISALLOW_COPY_AND_ASSIGN(TestingUpdateManifestProvider);
};

// Helper that observes the dictionary |pref| in local state and waits until the
// value stored for |key| matches |expected_value|.
class DictionaryPrefValueWaiter {
 public:
  DictionaryPrefValueWaiter(const std::string& pref,
                            const std::string& key,
                            const std::string& expected_value);
  ~DictionaryPrefValueWaiter();

  void Wait();

 private:
  void QuitLoopIfExpectedValueFound();

  const std::string pref_;
  const std::string key_;
  const std::string expected_value_;

  base::RunLoop run_loop_;
  PrefChangeRegistrar pref_change_registrar_;

  DISALLOW_COPY_AND_ASSIGN(DictionaryPrefValueWaiter);
};

TestingUpdateManifestProvider::Update::Update(const std::string& version,
                                              const GURL& crx_url)
    : version(version),
      crx_url(crx_url) {
}

TestingUpdateManifestProvider::Update::Update() {
}

TestingUpdateManifestProvider::TestingUpdateManifestProvider(
    const std::string& relative_update_url)
    : relative_update_url_(relative_update_url) {
}

void TestingUpdateManifestProvider::AddUpdate(const std::string& id,
                                              const std::string& version,
                                              const GURL& crx_url) {
  base::AutoLock auto_lock(lock_);
  updates_[id] = Update(version, crx_url);
}

std::unique_ptr<net::test_server::HttpResponse>
TestingUpdateManifestProvider::HandleRequest(
    const net::test_server::HttpRequest& request) {
  base::AutoLock auto_lock(lock_);
  const GURL url("http://localhost" + request.relative_url);
  if (url.path() != relative_update_url_)
    return std::unique_ptr<net::test_server::HttpResponse>();

  std::string content = kUpdateManifestHeader;
  for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
    if (it.GetKey() != "x")
      continue;
    // Extract the extension id from the subquery. Since GetValueForKeyInQuery()
    // expects a complete URL, dummy scheme and host must be prepended.
    std::string id;
    net::GetValueForKeyInQuery(GURL("http://dummy?" + it.GetUnescapedValue()),
                               "id", &id);
    UpdateMap::const_iterator entry = updates_.find(id);
    if (entry != updates_.end()) {
      content += base::StringPrintf(kUpdateManifestTemplate,
                                    id.c_str(),
                                    entry->second.crx_url.spec().c_str(),
                                    entry->second.version.c_str());
    }
  }
  content += kUpdateManifestFooter;
  std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
      new net::test_server::BasicHttpResponse);
  http_response->set_code(net::HTTP_OK);
  http_response->set_content(content);
  http_response->set_content_type("text/xml");
  return std::move(http_response);
}

TestingUpdateManifestProvider::~TestingUpdateManifestProvider() {
}

DictionaryPrefValueWaiter::DictionaryPrefValueWaiter(
    const std::string& pref,
    const std::string& key,
    const std::string& expected_value)
    : pref_(pref),
      key_(key),
      expected_value_(expected_value) {
  pref_change_registrar_.Init(g_browser_process->local_state());
}

DictionaryPrefValueWaiter::~DictionaryPrefValueWaiter() {
}

void DictionaryPrefValueWaiter::Wait() {
  pref_change_registrar_.Add(
      pref_.c_str(),
      base::Bind(&DictionaryPrefValueWaiter::QuitLoopIfExpectedValueFound,
                 base::Unretained(this)));
  QuitLoopIfExpectedValueFound();
  run_loop_.Run();
}

void DictionaryPrefValueWaiter::QuitLoopIfExpectedValueFound() {
  const base::DictionaryValue* pref =
      pref_change_registrar_.prefs()->GetDictionary(pref_.c_str());
  ASSERT_TRUE(pref);
  std::string actual_value;
  if (pref->GetStringWithoutPathExpansion(key_, &actual_value) &&
      actual_value == expected_value_) {
    run_loop_.Quit();
  }
}

bool DoesInstallFailureReferToId(const std::string& id,
                                 const content::NotificationSource& source,
                                 const content::NotificationDetails& details) {
  return content::Details<const extensions::CrxInstallError>(details)
             ->message()
             .find(base::UTF8ToUTF16(id)) != base::string16::npos;
}

std::unique_ptr<net::FakeURLFetcher> RunCallbackAndReturnFakeURLFetcher(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    const base::Closure& callback,
    const GURL& url,
    net::URLFetcherDelegate* delegate,
    const std::string& response_data,
    net::HttpStatusCode response_code,
    net::URLRequestStatus::Status status) {
  task_runner->PostTask(FROM_HERE, callback);
  return base::MakeUnique<net::FakeURLFetcher>(url, delegate, response_data,
                                               response_code, status);
}

bool IsSessionStarted() {
  return session_manager::SessionManager::Get()->IsSessionStarted();
}

void PolicyChangedCallback(const base::Closure& callback,
                           const base::Value* old_value,
                           const base::Value* new_value) {
  callback.Run();
}

}  // namespace

class DeviceLocalAccountTest : public DevicePolicyCrosBrowserTest,
                               public user_manager::UserManager::Observer,
                               public chrome::BrowserListObserver,
                               public extensions::AppWindowRegistry::Observer {
 protected:
  DeviceLocalAccountTest()
      : public_session_input_method_id_(
            base::StringPrintf(kPublicSessionInputMethodIDTemplate,
                               chromeos::extension_ime_util::kXkbExtensionId)),
        contents_(NULL) {
    set_exit_when_last_browser_closes(false);
  }

  ~DeviceLocalAccountTest() override {}

  void SetUp() override {
    // Configure and start the test server.
    std::unique_ptr<crypto::RSAPrivateKey> signing_key(
        PolicyBuilder::CreateTestSigningKey());
    ASSERT_TRUE(test_server_.SetSigningKeyAndSignature(
        signing_key.get(), PolicyBuilder::GetTestSigningKeySignature()));
    signing_key.reset();
    test_server_.RegisterClient(PolicyBuilder::kFakeToken,
                                PolicyBuilder::kFakeDeviceId);
    ASSERT_TRUE(test_server_.Start());

    BrowserList::AddObserver(this);

    DevicePolicyCrosBrowserTest::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(chromeos::switches::kLoginManager);
    command_line->AppendSwitch(chromeos::switches::kForceLoginManagerInTests);
    command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "user");
    command_line->AppendSwitchASCII(policy::switches::kDeviceManagementUrl,
                                    test_server_.GetServiceURL().spec());
  }

  void SetUpInProcessBrowserTestFixture() override {
    DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture();

    // Clear command-line arguments (but keep command-line switches) so the
    // startup pages policy takes effect.
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    base::CommandLine::StringVector argv(command_line->argv());
    argv.erase(argv.begin() + argv.size() - command_line->GetArgs().size(),
               argv.end());
    command_line->InitFromArgv(argv);

    InstallOwnerKey();
    MarkAsEnterpriseOwned();

    InitializePolicy();
  }

  void SetUpOnMainThread() override {
    DevicePolicyCrosBrowserTest::SetUpOnMainThread();

    initial_locale_ = g_browser_process->GetApplicationLocale();
    initial_language_ = l10n_util::GetLanguage(initial_locale_);

    content::WindowedNotificationObserver(
        chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
        content::NotificationService::AllSources()).Wait();

    chromeos::LoginDisplayHost* host =
        chromeos::LoginDisplayHost::default_host();
    ASSERT_TRUE(host);
    chromeos::WebUILoginView* web_ui_login_view = host->GetWebUILoginView();
    ASSERT_TRUE(web_ui_login_view);
    content::WebUI* web_ui = web_ui_login_view->GetWebUI();
    ASSERT_TRUE(web_ui);
    contents_ = web_ui->GetWebContents();
    ASSERT_TRUE(contents_);

    // Wait for the login UI to be ready.
    chromeos::OobeUI* oobe_ui = host->GetOobeUI();
    ASSERT_TRUE(oobe_ui);
    base::RunLoop run_loop;
    const bool oobe_ui_ready = oobe_ui->IsJSReady(run_loop.QuitClosure());
    if (!oobe_ui_ready)
      run_loop.Run();

    // The network selection screen changes the application locale on load and
    // once again on blur. Wait for the screen to load and blur it so that any
    // locale changes caused by this screen happen now and do not affect any
    // subsequent parts of the test.
    bool done = false;
    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
        contents_,
        "var languageSelect = document.getElementById('language-select');"
        "var blurAndReportSuccess = function() {"
        "  languageSelect.blur();"
        "  domAutomationController.send(true);"
        "};"
        "var screenLoading = document.getElementById('outer-container')"
        "    .classList.contains('down');"
        "if (document.activeElement == languageSelect || !screenLoading)"
        "  blurAndReportSuccess();"
        "else"
        "  languageSelect.addEventListener('focus', blurAndReportSuccess);",
        &done));

    // Skip to the login screen.
    chromeos::WizardController* wizard_controller =
        chromeos::WizardController::default_controller();
    ASSERT_TRUE(wizard_controller);
    wizard_controller->SkipToLoginForTesting(LoginScreenContext());

    chromeos::test::UserSessionManagerTestApi session_manager_test_api(
        chromeos::UserSessionManager::GetInstance());
    session_manager_test_api.SetShouldObtainTokenHandleInTests(false);
  }

  void TearDownOnMainThread() override {
    BrowserList::RemoveObserver(this);

    // This shuts down the login UI.
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(&chrome::AttemptExit));
    base::RunLoop().RunUntilIdle();
  }

  void LocalStateChanged(user_manager::UserManager* user_manager) override {
    if (run_loop_)
      run_loop_->Quit();
  }

  void OnBrowserRemoved(Browser* browser) override {
    if (run_loop_)
      run_loop_->Quit();
  }

  void OnAppWindowAdded(extensions::AppWindow* app_window) override {
    if (run_loop_)
      run_loop_->Quit();
  }

  void OnAppWindowRemoved(extensions::AppWindow* app_window) override {
    if (run_loop_)
      run_loop_->Quit();
  }

  void InitializePolicy() {
    device_policy()->policy_data().set_public_key_version(1);
    em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
    proto.mutable_show_user_names()->set_show_user_names(true);

    device_local_account_policy_.policy_data().set_policy_type(
        dm_protocol::kChromePublicAccountPolicyType);
    device_local_account_policy_.policy_data().set_username(kAccountId1);
    device_local_account_policy_.policy_data().set_settings_entity_id(
        kAccountId1);
    device_local_account_policy_.policy_data().set_public_key_version(1);
    device_local_account_policy_.payload().mutable_userdisplayname()->set_value(
        kDisplayName1);
  }

  void BuildDeviceLocalAccountPolicy() {
    device_local_account_policy_.SetDefaultSigningKey();
    device_local_account_policy_.Build();
  }

  void UploadDeviceLocalAccountPolicy() {
    BuildDeviceLocalAccountPolicy();
    ASSERT_TRUE(test_server_.UpdatePolicy(
        dm_protocol::kChromePublicAccountPolicyType, kAccountId1,
        device_local_account_policy_.payload().SerializeAsString()));
  }

  void UploadAndInstallDeviceLocalAccountPolicy() {
    UploadDeviceLocalAccountPolicy();
    session_manager_client()->set_device_local_account_policy(
        kAccountId1, device_local_account_policy_.GetBlob());
  }

  void SetRecommendedLocales(const char* const recommended_locales[],
                             size_t array_size) {
    em::StringListPolicyProto* session_locales_proto =
        device_local_account_policy_.payload().mutable_sessionlocales();
     session_locales_proto->mutable_policy_options()->set_mode(
        em::PolicyOptions_PolicyMode_RECOMMENDED);
    session_locales_proto->mutable_value()->Clear();
    for (size_t i = 0; i < array_size; ++i) {
      session_locales_proto->mutable_value()->add_entries(
          recommended_locales[i]);
    }
  }

  void AddPublicSessionToDevicePolicy(const std::string& username) {
    em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
    em::DeviceLocalAccountInfoProto* account =
        proto.mutable_device_local_accounts()->add_account();
    account->set_account_id(username);
    account->set_type(
        em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_PUBLIC_SESSION);
    RefreshDevicePolicy();
    ASSERT_TRUE(test_server_.UpdatePolicy(dm_protocol::kChromeDevicePolicyType,
                                          std::string(),
                                          proto.SerializeAsString()));
  }

  void EnableAutoLogin() {
    em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
    em::DeviceLocalAccountsProto* device_local_accounts =
        proto.mutable_device_local_accounts();
    device_local_accounts->set_auto_login_id(kAccountId1);
    device_local_accounts->set_auto_login_delay(0);
    RefreshDevicePolicy();
    ASSERT_TRUE(test_server_.UpdatePolicy(dm_protocol::kChromeDevicePolicyType,
                                          std::string(),
                                          proto.SerializeAsString()));
  }

  void CheckPublicSessionPresent(const AccountId& account_id) {
    const user_manager::User* user =
        user_manager::UserManager::Get()->FindUser(account_id);
    ASSERT_TRUE(user);
    EXPECT_EQ(account_id, user->GetAccountId());
    EXPECT_EQ(user_manager::USER_TYPE_PUBLIC_ACCOUNT, user->GetType());
  }

  base::FilePath GetExtensionCacheDirectoryForAccountID(
      const std::string& account_id) {
    base::FilePath extension_cache_root_dir;
    if (!PathService::Get(chromeos::DIR_DEVICE_LOCAL_ACCOUNT_EXTENSIONS,
                          &extension_cache_root_dir)) {
      ADD_FAILURE();
    }
    return extension_cache_root_dir.Append(
        base::HexEncode(account_id.c_str(), account_id.size()));
  }

  base::FilePath GetCacheCRXFilePath(const std::string& id,
                                     const std::string& version,
                                     const base::FilePath& path) {
    return path.Append(
        extensions::LocalExtensionCache::ExtensionFileName(id, version, ""));
  }

  base::FilePath GetCacheCRXFile(const std::string& account_id,
                                 const std::string& id,
                                 const std::string& version) {
    return GetCacheCRXFilePath(
        id, version, GetExtensionCacheDirectoryForAccountID(account_id));
  }

  // Returns a profile which can be used for testing.
  Profile* GetProfileForTest() {
    // Any profile can be used here since this test does not test multi profile.
    return ProfileManager::GetActiveUserProfile();
  }

  void WaitForDisplayName(const std::string& user_id,
                          const std::string& expected_display_name) {
    DictionaryPrefValueWaiter("UserDisplayName",
                              user_id,
                              expected_display_name).Wait();
  }

  void WaitForPolicy() {
    // Wait for the display name becoming available as that indicates
    // device-local account policy is fully loaded, which is a prerequisite for
    // successful login.
    WaitForDisplayName(account_id_1_.GetUserEmail(), kDisplayName1);
  }

  void ExpandPublicSessionPod(bool expect_advanced) {
    bool advanced = false;
    // Click on the pod to expand it.
    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
        contents_,
        base::StringPrintf(
            "var pod ="
            "    document.getElementById('pod-row').getPodWithUsername_('%s');"
            "pod.click();"
            "domAutomationController.send(pod.classList.contains('advanced'));",
            account_id_1_.Serialize().c_str()),
        &advanced));
    // Verify that the pod expanded to its basic/advanced form, as expected.
    EXPECT_EQ(expect_advanced, advanced);

    // Verify that the construction of the pod's language list did not affect
    // the current ICU locale.
    EXPECT_EQ(initial_language_, icu::Locale::getDefault().getLanguage());
  }

  // GetKeyboardLayoutsForLocale() posts a task to a background task runner.
  // This method flushes that task runner and the current thread's message loop
  // to ensure that GetKeyboardLayoutsForLocale() is finished.
  void WaitForGetKeyboardLayoutsForLocaleToFinish() {
    base::SequencedWorkerPool* worker_pool =
        content::BrowserThread::GetBlockingPool();
     scoped_refptr<base::SequencedTaskRunner> background_task_runner =
         worker_pool->GetSequencedTaskRunner(
             worker_pool->GetNamedSequenceToken(kSequenceToken));
    base::RunLoop run_loop;
    background_task_runner->PostTaskAndReply(FROM_HERE,
                                             base::Bind(&base::DoNothing),
                                             run_loop.QuitClosure());
    run_loop.Run();
    base::RunLoop().RunUntilIdle();

    // Verify that the construction of the keyboard layout list did not affect
    // the current ICU locale.
    EXPECT_EQ(initial_language_, icu::Locale::getDefault().getLanguage());
  }

  void StartLogin(const std::string& locale,
                  const std::string& input_method) {
    // Start login into the device-local account.
    chromeos::LoginDisplayHost* host =
        chromeos::LoginDisplayHost::default_host();
    ASSERT_TRUE(host);
    host->StartSignInScreen(LoginScreenContext());
    chromeos::ExistingUserController* controller =
        chromeos::ExistingUserController::current_controller();
    ASSERT_TRUE(controller);

    chromeos::UserContext user_context(user_manager::USER_TYPE_PUBLIC_ACCOUNT,
                                       account_id_1_);
    user_context.SetPublicSessionLocale(locale);
    user_context.SetPublicSessionInputMethod(input_method);
    controller->Login(user_context, chromeos::SigninSpecifics());
  }

  void WaitForSessionStart() {
    if (IsSessionStarted())
      return;
    content::WindowedNotificationObserver(chrome::NOTIFICATION_SESSION_STARTED,
                                          base::Bind(IsSessionStarted)).Wait();
  }

  void VerifyKeyboardLayoutMatchesLocale() {
    chromeos::input_method::InputMethodManager* input_method_manager =
        chromeos::input_method::InputMethodManager::Get();
    std::vector<std::string> layouts_from_locale;
    input_method_manager->GetInputMethodUtil()->
        GetInputMethodIdsFromLanguageCode(
            g_browser_process->GetApplicationLocale(),
            chromeos::input_method::kKeyboardLayoutsOnly,
            &layouts_from_locale);
    ASSERT_FALSE(layouts_from_locale.empty());
    EXPECT_EQ(layouts_from_locale.front(),
              input_method_manager->GetActiveIMEState()
                  ->GetCurrentInputMethod()
                  .id());
  }

  void RunWithRecommendedLocale(const char* const locales[],
                                size_t locales_size) {
    SetRecommendedLocales(locales, locales_size);
    UploadAndInstallDeviceLocalAccountPolicy();
    AddPublicSessionToDevicePolicy(kAccountId1);
    EnableAutoLogin();

    WaitForPolicy();

    WaitForSessionStart();

    EXPECT_EQ(locales[0], g_browser_process->GetApplicationLocale());
    EXPECT_EQ(l10n_util::GetLanguage(locales[0]),
              icu::Locale::getDefault().getLanguage());
    VerifyKeyboardLayoutMatchesLocale();
  }

  const AccountId account_id_1_ =
      AccountId::FromUserEmail(GenerateDeviceLocalAccountUserId(
          kAccountId1,
          DeviceLocalAccount::TYPE_PUBLIC_SESSION));
  const AccountId account_id_2_ =
      AccountId::FromUserEmail(GenerateDeviceLocalAccountUserId(
          kAccountId2,
          DeviceLocalAccount::TYPE_PUBLIC_SESSION));
  const std::string public_session_input_method_id_;

  std::string initial_locale_;
  std::string initial_language_;

  std::unique_ptr<base::RunLoop> run_loop_;

  UserPolicyBuilder device_local_account_policy_;
  LocalPolicyTestServer test_server_;

  content::WebContents* contents_;

 private:
  DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountTest);
};

static bool IsKnownUser(const AccountId& account_id) {
  return user_manager::UserManager::Get()->IsKnownUser(account_id);
}

// Helper that listen extension installation when new profile is created.
class ExtensionInstallObserver : public content::NotificationObserver,
                                 public extensions::ExtensionRegistryObserver {
 public:
  explicit ExtensionInstallObserver(const std::string& extension_id)
      : registry_(nullptr),
        waiting_extension_id_(extension_id),
        observed_(false) {
    registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
                   content::NotificationService::AllSources());
  }

  ~ExtensionInstallObserver() override {
    if (registry_ != nullptr)
      registry_->RemoveObserver(this);
  }

  // Wait until an extension with |extension_id| is installed.
  void Wait() {
    if (!observed_)
      run_loop_.Run();
  }

 private:
  // extensions::ExtensionRegistryObserver:
  void OnExtensionWillBeInstalled(content::BrowserContext* browser_context,
                                  const extensions::Extension* extension,
                                  bool is_update,
                                  const std::string& old_name) override {
    if (waiting_extension_id_ == extension->id()) {
      observed_ = true;
      run_loop_.Quit();
    }
  }

  // content::NotificationObserver:
  void Observe(int type,
               const content::NotificationSource& source,
               const content::NotificationDetails& details) override {
    DCHECK_EQ(chrome::NOTIFICATION_PROFILE_CREATED, type);

    Profile* profile = content::Source<Profile>(source).ptr();
    registry_ = extensions::ExtensionRegistry::Get(profile);

    // Check if extension is already installed with newly created profile.
    if (registry_->GetInstalledExtension(waiting_extension_id_)) {
      observed_ = true;
      run_loop_.Quit();
      return;
    }

    // Start listening for extension installation.
    registry_->AddObserver(this);
  }

  extensions::ExtensionRegistry* registry_;
  base::RunLoop run_loop_;
  content::NotificationRegistrar registrar_;
  std::string waiting_extension_id_;
  bool observed_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionInstallObserver);
};

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, LoginScreen) {
  AddPublicSessionToDevicePolicy(kAccountId1);
  AddPublicSessionToDevicePolicy(kAccountId2);

  content::WindowedNotificationObserver(chrome::NOTIFICATION_USER_LIST_CHANGED,
                                        base::Bind(&IsKnownUser, account_id_1_))
      .Wait();
  EXPECT_TRUE(IsKnownUser(account_id_2_));

  CheckPublicSessionPresent(account_id_1_);
  CheckPublicSessionPresent(account_id_2_);

  ASSERT_TRUE(user_manager::UserManager::Get()->FindUser(account_id_1_));
  EXPECT_TRUE(user_manager::UserManager::Get()
                  ->FindUser(account_id_1_)
                  ->IsAffiliated());

  ASSERT_TRUE(user_manager::UserManager::Get()->FindUser(account_id_2_));
  EXPECT_TRUE(user_manager::UserManager::Get()
                  ->FindUser(account_id_2_)
                  ->IsAffiliated());
}

// Flaky: http://crbug.com/512670.
IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, DISABLED_DisplayName) {
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  // Verify that the display name is shown in the UI.
  const std::string get_compact_pod_display_name = base::StringPrintf(
      "domAutomationController.send(document.getElementById('pod-row')"
      "    .getPodWithUsername_('%s').nameElement.textContent);",
      account_id_1_.Serialize().c_str());
  std::string display_name;
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
      contents_,
      get_compact_pod_display_name,
      &display_name));
  EXPECT_EQ(kDisplayName1, display_name);
  const std::string get_expanded_pod_display_name = base::StringPrintf(
      "domAutomationController.send(document.getElementById('pod-row')"
      "    .getPodWithUsername_('%s').querySelector('.expanded-pane-name')"
      "        .textContent);",
      account_id_1_.Serialize().c_str());
  display_name.clear();
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
      contents_,
      get_expanded_pod_display_name,
      &display_name));
  EXPECT_EQ(kDisplayName1, display_name);

  // Click on the pod to expand it.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "document.getElementById('pod-row').getPodWithUsername_('%s')"
          "    .click();",
          account_id_1_.Serialize().c_str())));

  // Change the display name.
  device_local_account_policy_.payload().mutable_userdisplayname()->set_value(
      kDisplayName2);
  UploadAndInstallDeviceLocalAccountPolicy();
  policy::BrowserPolicyConnectorChromeOS* connector =
      g_browser_process->platform_part()->browser_policy_connector_chromeos();
  DeviceLocalAccountPolicyBroker* broker =
      connector->GetDeviceLocalAccountPolicyService()->GetBrokerForUser(
          account_id_1_.GetUserEmail());
  ASSERT_TRUE(broker);
  broker->core()->store()->Load();
  WaitForDisplayName(account_id_1_.GetUserEmail(), kDisplayName2);

  // Verify that the new display name is shown in the UI.
  display_name.clear();
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
      contents_,
      get_compact_pod_display_name,
      &display_name));
  EXPECT_EQ(kDisplayName2, display_name);
  display_name.clear();
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
      contents_,
      get_expanded_pod_display_name,
      &display_name));
  EXPECT_EQ(kDisplayName2, display_name);

  // Verify that the pod is still expanded. This indicates that the UI updated
  // without reloading and losing state.
  bool expanded = false;
  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
      contents_,
      base::StringPrintf(
          "domAutomationController.send(document.getElementById('pod-row')"
          "    .getPodWithUsername_('%s').expanded);",
          account_id_1_.Serialize().c_str()),
      &expanded));
  EXPECT_TRUE(expanded);
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, PolicyDownload) {
  UploadDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  // Sanity check: The policy should be present now.
  ASSERT_FALSE(session_manager_client()->device_local_account_policy(
      kAccountId1).empty());
}

static bool IsNotKnownUser(const AccountId& account_id) {
  return !IsKnownUser(account_id);
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, AccountListChange) {
  AddPublicSessionToDevicePolicy(kAccountId1);
  AddPublicSessionToDevicePolicy(kAccountId2);

  content::WindowedNotificationObserver(chrome::NOTIFICATION_USER_LIST_CHANGED,
                                        base::Bind(&IsKnownUser, account_id_1_))
      .Wait();
  EXPECT_TRUE(IsKnownUser(account_id_2_));

  // Update policy to remove kAccountId2.
  em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
  proto.mutable_device_local_accounts()->clear_account();
  AddPublicSessionToDevicePolicy(kAccountId1);

  em::ChromeDeviceSettingsProto policy;
  policy.mutable_show_user_names()->set_show_user_names(true);
  em::DeviceLocalAccountInfoProto* account1 =
      policy.mutable_device_local_accounts()->add_account();
  account1->set_account_id(kAccountId1);
  account1->set_type(
      em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_PUBLIC_SESSION);

  test_server_.UpdatePolicy(dm_protocol::kChromeDevicePolicyType, std::string(),
                            policy.SerializeAsString());
  g_browser_process->policy_service()->RefreshPolicies(base::Closure());

  // Make sure the second device-local account disappears.
  content::WindowedNotificationObserver(
      chrome::NOTIFICATION_USER_LIST_CHANGED,
      base::Bind(&IsNotKnownUser, account_id_2_))
      .Wait();
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, StartSession) {
  // Specify startup pages.
  device_local_account_policy_.payload().mutable_restoreonstartup()->set_value(
      SessionStartupPref::kPrefValueURLs);
  em::StringListPolicyProto* startup_urls_proto =
      device_local_account_policy_.payload().mutable_restoreonstartupurls();
  for (size_t i = 0; i < arraysize(kStartupURLs); ++i)
    startup_urls_proto->mutable_value()->add_entries(kStartupURLs[i]);
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));
  WaitForSessionStart();

  // Check that the startup pages specified in policy were opened.
  BrowserList* browser_list = BrowserList::GetInstance();
  EXPECT_EQ(1U, browser_list->size());
  Browser* browser = browser_list->get(0);
  ASSERT_TRUE(browser);

  TabStripModel* tabs = browser->tab_strip_model();
  ASSERT_TRUE(tabs);
  int expected_tab_count = static_cast<int>(arraysize(kStartupURLs));
  EXPECT_EQ(expected_tab_count, tabs->count());
  for (int i = 0; i < expected_tab_count && i < tabs->count(); ++i) {
    EXPECT_EQ(GURL(kStartupURLs[i]),
              tabs->GetWebContentsAt(i)->GetVisibleURL());
  }

  // Verify that the session is not considered to be logged in with a GAIA
  // account.
  Profile* profile = GetProfileForTest();
  ASSERT_TRUE(profile);
  EXPECT_FALSE(
      SigninManagerFactory::GetForProfile(profile)->IsAuthenticated());
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, FullscreenDisallowed) {
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));
  WaitForSessionStart();

  BrowserList* browser_list = BrowserList::GetInstance();
  EXPECT_EQ(1U, browser_list->size());
  Browser* browser = browser_list->get(0);
  ASSERT_TRUE(browser);
  BrowserWindow* browser_window = browser->window();
  ASSERT_TRUE(browser_window);

  // Verify that an attempt to enter fullscreen mode is denied.
  EXPECT_FALSE(browser_window->IsFullscreen());
  chrome::ToggleFullscreenMode(browser);
  EXPECT_FALSE(browser_window->IsFullscreen());
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionsUncached) {
  // Make it possible to force-install a hosted app and an extension.
  ASSERT_TRUE(embedded_test_server()->Start());
  scoped_refptr<TestingUpdateManifestProvider> testing_update_manifest_provider(
      new TestingUpdateManifestProvider(kRelativeUpdateURL));
  testing_update_manifest_provider->AddUpdate(
      kHostedAppID, kHostedAppVersion,
      embedded_test_server()->GetURL(std::string("/") + kHostedAppCRXPath));
  testing_update_manifest_provider->AddUpdate(
      kGoodExtensionID, kGoodExtensionVersion,
      embedded_test_server()->GetURL(std::string("/") + kGoodExtensionCRXPath));
  embedded_test_server()->RegisterRequestHandler(
      base::Bind(&TestingUpdateManifestProvider::HandleRequest,
                 testing_update_manifest_provider));

  // Specify policy to force-install the hosted app and the extension.
  em::StringList* forcelist = device_local_account_policy_.payload()
      .mutable_extensioninstallforcelist()->mutable_value();
  forcelist->add_entries(base::StringPrintf(
      "%s;%s",
      kHostedAppID,
      embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str()));
  forcelist->add_entries(base::StringPrintf(
      "%s;%s",
      kGoodExtensionID,
      embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str()));

  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  // Start listening for app/extension installation results.
  ExtensionInstallObserver install_observer(kHostedAppID);
  content::WindowedNotificationObserver extension_observer(
      extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
      base::Bind(DoesInstallFailureReferToId, kGoodExtensionID));
  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));

  // Wait for the hosted app installation to succeed and the extension
  // installation to fail (because hosted apps are whitelisted for use in
  // device-local accounts and extensions are not).
  install_observer.Wait();
  extension_observer.Wait();

  // Verify that the hosted app was installed.
  Profile* profile = GetProfileForTest();
  ASSERT_TRUE(profile);
  ExtensionService* extension_service =
      extensions::ExtensionSystem::Get(profile)->extension_service();
  EXPECT_TRUE(extension_service->GetExtensionById(kHostedAppID, true));

  // Verify that the extension was not installed.
  EXPECT_FALSE(extension_service->GetExtensionById(kGoodExtensionID, true));

  // Verify that the app was downloaded to the account's extension cache.
  base::FilePath test_dir;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  EXPECT_TRUE(ContentsEqual(
          GetCacheCRXFile(kAccountId1, kHostedAppID, kHostedAppVersion),
          test_dir.Append(kHostedAppCRXPath)));

  // Verify that the extension was removed from the account's extension cache
  // after the installation failure.
  DeviceLocalAccountPolicyBroker* broker =
      g_browser_process->platform_part()
          ->browser_policy_connector_chromeos()
          ->GetDeviceLocalAccountPolicyService()
          ->GetBrokerForUser(account_id_1_.GetUserEmail());
  ASSERT_TRUE(broker);
  chromeos::ExternalCache* cache =
      broker->extension_loader()->GetExternalCacheForTesting();
  ASSERT_TRUE(cache);
  EXPECT_FALSE(cache->GetExtension(kGoodExtensionID, NULL, NULL));
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionsCached) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Pre-populate the device local account's extension cache with a hosted app
  // and an extension.
  EXPECT_TRUE(base::CreateDirectory(
      GetExtensionCacheDirectoryForAccountID(kAccountId1)));
  base::FilePath test_dir;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  const base::FilePath cached_hosted_app =
      GetCacheCRXFile(kAccountId1, kHostedAppID, kHostedAppVersion);
  EXPECT_TRUE(CopyFile(test_dir.Append(kHostedAppCRXPath),
                       cached_hosted_app));
  EXPECT_TRUE(CopyFile(
      test_dir.Append(kGoodExtensionCRXPath),
      GetCacheCRXFile(kAccountId1, kGoodExtensionID, kGoodExtensionVersion)));

  // Specify policy to force-install the hosted app.
  em::StringList* forcelist = device_local_account_policy_.payload()
      .mutable_extensioninstallforcelist()->mutable_value();
  forcelist->add_entries(base::StringPrintf(
      "%s;%s",
      kHostedAppID,
      embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str()));
  forcelist->add_entries(base::StringPrintf(
      "%s;%s",
      kGoodExtensionID,
      embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str()));

  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  // Start listening for app/extension installation results.
  ExtensionInstallObserver install_observer(kHostedAppID);
  content::WindowedNotificationObserver extension_observer(
      extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
      base::Bind(DoesInstallFailureReferToId, kGoodExtensionID));

  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));

  // Wait for the hosted app installation to succeed and the extension
  // installation to fail.
  install_observer.Wait();
  extension_observer.Wait();

  // Verify that the hosted app was installed.
  Profile* profile = GetProfileForTest();
  ASSERT_TRUE(profile);
  ExtensionService* extension_service =
      extensions::ExtensionSystem::Get(profile)->extension_service();
  EXPECT_TRUE(extension_service->GetExtensionById(kHostedAppID, true));

  // Verify that the extension was not installed.
  EXPECT_FALSE(extension_service->GetExtensionById(kGoodExtensionID, true));

  // Verify that the app is still in the account's extension cache.
  EXPECT_TRUE(PathExists(cached_hosted_app));

  // Verify that the extension was removed from the account's extension cache.
  DeviceLocalAccountPolicyBroker* broker =
      g_browser_process->platform_part()
          ->browser_policy_connector_chromeos()
          ->GetDeviceLocalAccountPolicyService()
          ->GetBrokerForUser(account_id_1_.GetUserEmail());
  ASSERT_TRUE(broker);
  chromeos::ExternalCache* cache =
      broker->extension_loader()->GetExternalCacheForTesting();
  ASSERT_TRUE(cache);
  EXPECT_FALSE(cache->GetExtension(kGoodExtensionID, NULL, NULL));
}

static void OnPutExtension(std::unique_ptr<base::RunLoop>* run_loop,
                           const base::FilePath& file_path,
                           bool file_ownership_passed) {
  ASSERT_TRUE(*run_loop);
  (*run_loop)->Quit();
}

static void OnExtensionCacheImplInitialized(
    std::unique_ptr<base::RunLoop>* run_loop) {
  ASSERT_TRUE(*run_loop);
  (*run_loop)->Quit();
}

static void CreateFile(const base::FilePath& file,
                size_t size,
                const base::Time& timestamp) {
  std::string data(size, 0);
  EXPECT_EQ(base::WriteFile(file, data.data(), data.size()), int(size));
  EXPECT_TRUE(base::TouchFile(file, timestamp, timestamp));
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionCacheImplTest) {
  // Make it possible to force-install a hosted app and an extension.
  ASSERT_TRUE(embedded_test_server()->Start());
  scoped_refptr<TestingUpdateManifestProvider> testing_update_manifest_provider(
      new TestingUpdateManifestProvider(kRelativeUpdateURL));
  testing_update_manifest_provider->AddUpdate(
      kHostedAppID, kHostedAppVersion,
      embedded_test_server()->GetURL(std::string("/") + kHostedAppCRXPath));
  testing_update_manifest_provider->AddUpdate(
      kGoodExtensionID, kGoodExtensionVersion,
      embedded_test_server()->GetURL(std::string("/") + kGoodExtensionCRXPath));
  embedded_test_server()->RegisterRequestHandler(
      base::Bind(&TestingUpdateManifestProvider::HandleRequest,
                 testing_update_manifest_provider));
  // Create and initialize local cache.
  base::ScopedTempDir cache_dir;
  EXPECT_TRUE(cache_dir.CreateUniqueTempDir());
  const base::FilePath impl_path = cache_dir.GetPath();
  EXPECT_TRUE(base::CreateDirectory(impl_path));
  CreateFile(impl_path.Append(
                 extensions::LocalExtensionCache::kCacheReadyFlagFileName),
             0, base::Time::Now());
  extensions::ExtensionCacheImpl cache_impl(
      base::MakeUnique<extensions::ChromeOSExtensionCacheDelegate>(impl_path));
  std::unique_ptr<base::RunLoop> run_loop;
  run_loop.reset(new base::RunLoop);
  cache_impl.Start(base::Bind(&OnExtensionCacheImplInitialized, &run_loop));
  run_loop->Run();

  // Put extension in the local cache.
  base::ScopedTempDir temp_dir;
  EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath temp_path = temp_dir.GetPath();
  EXPECT_TRUE(base::CreateDirectory(temp_path));
  const base::FilePath temp_file =
      GetCacheCRXFilePath(kGoodExtensionID, kGoodExtensionVersion, temp_path);
  base::FilePath test_dir;
  std::string hash;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  EXPECT_TRUE(CopyFile(test_dir.Append(kGoodExtensionCRXPath), temp_file));
  cache_impl.AllowCaching(kGoodExtensionID);
  run_loop.reset(new base::RunLoop);
  cache_impl.PutExtension(kGoodExtensionID, hash, temp_file,
                          kGoodExtensionVersion,
                          base::Bind(&OnPutExtension, &run_loop));
  run_loop->Run();

  // Verify that the extension file was added to the local cache.
  const base::FilePath local_file =
      GetCacheCRXFilePath(kGoodExtensionID, kGoodExtensionVersion, impl_path);
  EXPECT_TRUE(PathExists(local_file));

  // Specify policy to force-install the hosted app and the extension.
  em::StringList* forcelist = device_local_account_policy_.payload()
      .mutable_extensioninstallforcelist()->mutable_value();
  forcelist->add_entries(base::StringPrintf(
      "%s;%s",
      kHostedAppID,
      embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str()));
  forcelist->add_entries(base::StringPrintf(
      "%s;%s",
      kGoodExtensionID,
      embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str()));

  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  // Start listening for app/extension installation results.
  ExtensionInstallObserver install_observer(kHostedAppID);
  content::WindowedNotificationObserver extension_observer(
      extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
      base::Bind(DoesInstallFailureReferToId, kGoodExtensionID));

  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));

  // Wait for the hosted app installation to succeed and the extension
  // installation to fail (because hosted apps are whitelisted for use in
  // device-local accounts and extensions are not).
  install_observer.Wait();
  extension_observer.Wait();

  // Verify that the extension was kept in the local cache.
  EXPECT_TRUE(cache_impl.GetExtension(kGoodExtensionID, hash, NULL, NULL));

  // Verify that the extension file was kept in the local cache.
  EXPECT_TRUE(PathExists(local_file));
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExternalData) {
  // user_manager::UserManager requests an external data fetch whenever
  // the key::kUserAvatarImage policy is set. Since this test wants to
  // verify that the underlying policy subsystem will start a fetch
  // without this request as well, the user_manager::UserManager must be
  // prevented from seeing the policy change.
  reinterpret_cast<chromeos::ChromeUserManagerImpl*>(
      user_manager::UserManager::Get())->StopPolicyObserverForTesting();

  UploadDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  // Start serving external data at |kExternalDataURL|.
  std::unique_ptr<base::RunLoop> run_loop(new base::RunLoop);
  std::unique_ptr<net::FakeURLFetcherFactory> fetcher_factory(
      new net::FakeURLFetcherFactory(
          NULL, base::Bind(&RunCallbackAndReturnFakeURLFetcher,
                           base::ThreadTaskRunnerHandle::Get(),
                           run_loop->QuitClosure())));
  fetcher_factory->SetFakeResponse(GURL(kExternalDataURL),
                                   kExternalData,
                                   net::HTTP_OK,
                                   net::URLRequestStatus::SUCCESS);

  // Specify an external data reference for the key::kUserAvatarImage policy.
  std::unique_ptr<base::DictionaryValue> metadata =
      test::ConstructExternalDataReference(kExternalDataURL, kExternalData);
  std::string policy;
  base::JSONWriter::Write(*metadata, &policy);
  device_local_account_policy_.payload().mutable_useravatarimage()->set_value(
      policy);
  UploadAndInstallDeviceLocalAccountPolicy();
  policy::BrowserPolicyConnectorChromeOS* connector =
      g_browser_process->platform_part()->browser_policy_connector_chromeos();
  DeviceLocalAccountPolicyBroker* broker =
      connector->GetDeviceLocalAccountPolicyService()->GetBrokerForUser(
          account_id_1_.GetUserEmail());
  ASSERT_TRUE(broker);
  broker->core()->store()->Load();

  // The external data should be fetched and cached automatically. Wait for this
  // fetch.
  run_loop->Run();

  // Stop serving external data at |kExternalDataURL|.
  fetcher_factory.reset();

  const PolicyMap::Entry* policy_entry =
      broker->core()->store()->policy_map().Get(key::kUserAvatarImage);
  ASSERT_TRUE(policy_entry);
  ASSERT_TRUE(policy_entry->external_data_fetcher);

  // Retrieve the external data. Although the data is no longer being served at
  // |kExternalDataURL|, the retrieval should succeed because the data has been
  // cached.
  run_loop.reset(new base::RunLoop);
  std::unique_ptr<std::string> fetched_external_data;
  policy_entry->external_data_fetcher->Fetch(base::Bind(
      &test::ExternalDataFetchCallback,
      &fetched_external_data,
      run_loop->QuitClosure()));
  run_loop->Run();

  ASSERT_TRUE(fetched_external_data);
  EXPECT_EQ(kExternalData, *fetched_external_data);

  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));
  WaitForSessionStart();

  // Verify that the external data reference has propagated to the device-local
  // account's ProfilePolicyConnector.
  ProfilePolicyConnector* policy_connector =
      ProfilePolicyConnectorFactory::GetForBrowserContext(GetProfileForTest());
  ASSERT_TRUE(policy_connector);
  const PolicyMap& policies = policy_connector->policy_service()->GetPolicies(
      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
  policy_entry = policies.Get(key::kUserAvatarImage);
  ASSERT_TRUE(policy_entry);
  EXPECT_TRUE(base::Value::Equals(metadata.get(), policy_entry->value.get()));
  ASSERT_TRUE(policy_entry->external_data_fetcher);

  // Retrieve the external data via the ProfilePolicyConnector. The retrieval
  // should succeed because the data has been cached.
  run_loop.reset(new base::RunLoop);
  fetched_external_data.reset();
  policy_entry->external_data_fetcher->Fetch(base::Bind(
      &test::ExternalDataFetchCallback,
      &fetched_external_data,
      run_loop->QuitClosure()));
  run_loop->Run();

  ASSERT_TRUE(fetched_external_data);
  EXPECT_EQ(kExternalData, *fetched_external_data);
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, UserAvatarImage) {
  ASSERT_TRUE(embedded_test_server()->Start());

  UploadDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  base::FilePath test_dir;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  std::string image_data;
  ASSERT_TRUE(base::ReadFileToString(
      test_dir.Append(chromeos::test::kUserAvatarImage1RelativePath),
      &image_data));

  std::string policy;
  base::JSONWriter::Write(
      *test::ConstructExternalDataReference(
          embedded_test_server()
              ->GetURL(std::string("/") +
                       chromeos::test::kUserAvatarImage1RelativePath)
              .spec(),
          image_data),
      &policy);
  device_local_account_policy_.payload().mutable_useravatarimage()->set_value(
      policy);
  UploadAndInstallDeviceLocalAccountPolicy();
  policy::BrowserPolicyConnectorChromeOS* connector =
      g_browser_process->platform_part()->browser_policy_connector_chromeos();
  DeviceLocalAccountPolicyBroker* broker =
      connector->GetDeviceLocalAccountPolicyService()->GetBrokerForUser(
          account_id_1_.GetUserEmail());
  ASSERT_TRUE(broker);

  run_loop_.reset(new base::RunLoop);
  user_manager::UserManager::Get()->AddObserver(this);
  broker->core()->store()->Load();
  run_loop_->Run();
  user_manager::UserManager::Get()->RemoveObserver(this);

  std::unique_ptr<gfx::ImageSkia> policy_image =
      chromeos::test::ImageLoader(
          test_dir.Append(chromeos::test::kUserAvatarImage1RelativePath))
          .Load();
  ASSERT_TRUE(policy_image);

  const user_manager::User* user =
      user_manager::UserManager::Get()->FindUser(account_id_1_);
  ASSERT_TRUE(user);

  base::FilePath user_data_dir;
  ASSERT_TRUE(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
  const base::FilePath saved_image_path =
      user_data_dir.Append(account_id_1_.GetUserEmail()).AddExtension("jpg");

  EXPECT_FALSE(user->HasDefaultImage());
  EXPECT_EQ(user_manager::User::USER_IMAGE_EXTERNAL, user->image_index());
  EXPECT_TRUE(chromeos::test::AreImagesEqual(*policy_image, user->GetImage()));
  const base::DictionaryValue* images_pref =
      g_browser_process->local_state()->GetDictionary("user_image_info");
  ASSERT_TRUE(images_pref);
  const base::DictionaryValue* image_properties;
  ASSERT_TRUE(images_pref->GetDictionaryWithoutPathExpansion(
      account_id_1_.GetUserEmail(), &image_properties));
  int image_index;
  std::string image_path;
  ASSERT_TRUE(image_properties->GetInteger("index", &image_index));
  ASSERT_TRUE(image_properties->GetString("path", &image_path));
  EXPECT_EQ(user_manager::User::USER_IMAGE_EXTERNAL, image_index);
  EXPECT_EQ(saved_image_path.value(), image_path);

  std::unique_ptr<gfx::ImageSkia> saved_image =
      chromeos::test::ImageLoader(saved_image_path).Load();
  ASSERT_TRUE(saved_image);

  // Check image dimensions. Images can't be compared since JPEG is lossy.
  EXPECT_EQ(policy_image->width(), saved_image->width());
  EXPECT_EQ(policy_image->height(), saved_image->height());
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, LastWindowClosedLogoutReminder) {
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));
  WaitForSessionStart();

  Profile* profile = GetProfileForTest();
  ASSERT_TRUE(profile);
  extensions::AppWindowRegistry* app_window_registry =
      extensions::AppWindowRegistry::Get(profile);
  app_window_registry->AddObserver(this);

  // Verify that the logout confirmation dialog is not showing.
  ash::LogoutConfirmationController* logout_confirmation_controller =
      ash::WmShell::Get()->logout_confirmation_controller();
  ASSERT_TRUE(logout_confirmation_controller);
  EXPECT_FALSE(logout_confirmation_controller->dialog_for_testing());

  // Remove policy that allows only explicitly whitelisted apps to be installed
  // in a public session.
  extensions::ExtensionSystem* extension_system =
      extensions::ExtensionSystem::Get(profile);
  ASSERT_TRUE(extension_system);
  extension_system->management_policy()->UnregisterAllProviders();

  // Install and a platform app.
  scoped_refptr<extensions::CrxInstaller> installer =
      extensions::CrxInstaller::CreateSilent(
          extension_system->extension_service());
  installer->set_allow_silent_install(true);
  installer->set_install_cause(extension_misc::INSTALL_CAUSE_USER_DOWNLOAD);
  installer->set_creation_flags(extensions::Extension::FROM_WEBSTORE);
  content::WindowedNotificationObserver app_install_observer(
      extensions::NOTIFICATION_CRX_INSTALLER_DONE,
      content::NotificationService::AllSources());
  base::FilePath test_dir;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  installer->InstallCrx(test_dir.Append(kPackagedAppCRXPath));
  app_install_observer.Wait();
  const extensions::Extension* app =
      content::Details<const extensions::Extension>(
          app_install_observer.details()).ptr();

  // Start the platform app, causing it to open a window.
  run_loop_.reset(new base::RunLoop);
  OpenApplication(AppLaunchParams(
      profile, app, extensions::LAUNCH_CONTAINER_NONE,
      WindowOpenDisposition::NEW_WINDOW, extensions::SOURCE_TEST));
  run_loop_->Run();
  EXPECT_EQ(1U, app_window_registry->app_windows().size());

  // Close the only open browser window.
  BrowserList* browser_list = BrowserList::GetInstance();
  EXPECT_EQ(1U, browser_list->size());
  Browser* browser = browser_list->get(0);
  ASSERT_TRUE(browser);
  BrowserWindow* browser_window = browser->window();
  ASSERT_TRUE(browser_window);
  run_loop_.reset(new base::RunLoop);
  browser_window->Close();
  browser_window = NULL;
  run_loop_->Run();
  browser = NULL;
  EXPECT_TRUE(browser_list->empty());

  // Verify that the logout confirmation dialog is not showing because an app
  // window is still open.
  EXPECT_FALSE(logout_confirmation_controller->dialog_for_testing());

  // Open a browser window.
  Browser* first_browser = CreateBrowser(profile);
  EXPECT_EQ(1U, browser_list->size());

  // Close the app window.
  run_loop_.reset(new base::RunLoop);
  ASSERT_EQ(1U, app_window_registry->app_windows().size());
  app_window_registry->app_windows().front()->GetBaseWindow()->Close();
  run_loop_->Run();
  EXPECT_TRUE(app_window_registry->app_windows().empty());

  // Verify that the logout confirmation dialog is not showing because a browser
  // window is still open.
  EXPECT_FALSE(logout_confirmation_controller->dialog_for_testing());

  // Open a second browser window.
  Browser* second_browser = CreateBrowser(profile);
  EXPECT_EQ(2U, browser_list->size());

  // Close the first browser window.
  browser_window = first_browser->window();
  ASSERT_TRUE(browser_window);
  run_loop_.reset(new base::RunLoop);
  browser_window->Close();
  browser_window = NULL;
  run_loop_->Run();
  first_browser = NULL;
  EXPECT_EQ(1U, browser_list->size());

  // Verify that the logout confirmation dialog is not showing because a browser
  // window is still open.
  EXPECT_FALSE(logout_confirmation_controller->dialog_for_testing());

  // Close the second browser window.
  browser_window = second_browser->window();
  ASSERT_TRUE(browser_window);
  run_loop_.reset(new base::RunLoop);
  browser_window->Close();
  browser_window = NULL;
  run_loop_->Run();
  second_browser = NULL;
  EXPECT_TRUE(browser_list->empty());

  // Verify that the logout confirmation dialog is showing.
  ash::LogoutConfirmationDialog* dialog =
      logout_confirmation_controller->dialog_for_testing();
  ASSERT_TRUE(dialog);

  // Deny the logout.
  dialog->GetWidget()->Close();
  dialog = NULL;
  base::RunLoop().RunUntilIdle();

  // Verify that the logout confirmation dialog is no longer showing.
  EXPECT_FALSE(logout_confirmation_controller->dialog_for_testing());

  // Open a browser window.
  browser = CreateBrowser(profile);
  EXPECT_EQ(1U, browser_list->size());

  // Close the browser window.
  browser_window = browser->window();
  ASSERT_TRUE(browser_window);
  run_loop_.reset(new base::RunLoop);
  browser_window->Close();
  browser_window = NULL;
  run_loop_->Run();
  browser = NULL;
  EXPECT_TRUE(browser_list->empty());

  // Verify that the logout confirmation dialog is showing again.
  dialog = logout_confirmation_controller->dialog_for_testing();
  ASSERT_TRUE(dialog);

  // Deny the logout.
  dialog->GetWidget()->Close();
  dialog = NULL;
  base::RunLoop().RunUntilIdle();

  app_window_registry->RemoveObserver(this);
};

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, NoRecommendedLocaleNoSwitch) {
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  ExpandPublicSessionPod(false);

  // Click the enter button to start the session.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "document.getElementById('pod-row').getPodWithUsername_('%s')"
          "    .querySelector('.enter-button').click();",
          account_id_1_.Serialize().c_str())));

  WaitForSessionStart();

  // Verify that the locale has not changed and the first keyboard layout
  // applicable to the locale was chosen.
  EXPECT_EQ(initial_locale_, g_browser_process->GetApplicationLocale());
  EXPECT_EQ(initial_language_, icu::Locale::getDefault().getLanguage());
  VerifyKeyboardLayoutMatchesLocale();
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, NoRecommendedLocaleSwitch) {
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  ExpandPublicSessionPod(false);

  // Click the link that switches the pod to its advanced form. Verify that the
  // pod switches from basic to advanced.
  bool advanced = false;
  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
      contents_,
      base::StringPrintf(
          "var pod ="
          "    document.getElementById('pod-row').getPodWithUsername_('%s');"
          "pod.querySelector('.language-and-input').click();"
          "domAutomationController.send(pod.classList.contains('advanced'));",
          account_id_1_.Serialize().c_str()),
      &advanced));
  EXPECT_FALSE(advanced);

  // Manually select a different locale.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "var languageSelect = document.getElementById('pod-row')"
          "    .getPodWithUsername_('%s').querySelector('.language-select');"
          "languageSelect.value = '%s';"
          "var event = document.createEvent('HTMLEvents');"
          "event.initEvent('change', false, true);"
          "languageSelect.dispatchEvent(event);",
          account_id_1_.Serialize().c_str(), kPublicSessionLocale)));

  // The UI will have requested an updated list of keyboard layouts at this
  // point. Wait for the constructions of this list to finish.
  WaitForGetKeyboardLayoutsForLocaleToFinish();

  // Manually select a different keyboard layout and click the enter button to
  // start the session.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "var pod ="
          "    document.getElementById('pod-row').getPodWithUsername_('%s');"
          "pod.querySelector('.keyboard-select').value = '%s';"
          "pod.querySelector('.enter-button').click();",
          account_id_1_.Serialize().c_str(),
          public_session_input_method_id_.c_str())));

  WaitForSessionStart();

  // Verify that the locale and keyboard layout have been applied.
  EXPECT_EQ(kPublicSessionLocale, g_browser_process->GetApplicationLocale());
  EXPECT_EQ(l10n_util::GetLanguage(kPublicSessionLocale),
            icu::Locale::getDefault().getLanguage());
  EXPECT_EQ(public_session_input_method_id_,
            chromeos::input_method::InputMethodManager::Get()
                ->GetActiveIMEState()
                ->GetCurrentInputMethod()
                .id());
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, OneRecommendedLocale) {
  // Specify a recommended locale.
  SetRecommendedLocales(kSingleRecommendedLocale,
                        arraysize(kSingleRecommendedLocale));
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  ExpandPublicSessionPod(false);

  // Click the enter button to start the session.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "document.getElementById('pod-row').getPodWithUsername_('%s')"
          "    .querySelector('.enter-button').click();",
          account_id_1_.Serialize().c_str())));

  WaitForSessionStart();

  // Verify that the recommended locale has been applied and the first keyboard
  // layout applicable to the locale was chosen.
  EXPECT_EQ(kSingleRecommendedLocale[0],
            g_browser_process->GetApplicationLocale());
  EXPECT_EQ(l10n_util::GetLanguage(kSingleRecommendedLocale[0]),
            icu::Locale::getDefault().getLanguage());
  VerifyKeyboardLayoutMatchesLocale();
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, MultipleRecommendedLocales) {
  // Specify recommended locales.
  SetRecommendedLocales(kRecommendedLocales1, arraysize(kRecommendedLocales1));
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);
  AddPublicSessionToDevicePolicy(kAccountId2);

  WaitForPolicy();

  ExpandPublicSessionPod(true);

  // Verify that the pod shows a list of locales beginning with the recommended
  // ones, followed by others.
  const std::string get_locale_list = base::StringPrintf(
      "var languageSelect = document.getElementById('pod-row')"
      "    .getPodWithUsername_('%s').querySelector('.language-select');"
      "var locales = [];"
      "for (var i = 0; i < languageSelect.length; ++i)"
      "  locales.push(languageSelect.options[i].value);"
      "domAutomationController.send(JSON.stringify(locales));",
      account_id_1_.Serialize().c_str());
  std::string json;
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(contents_,
                                                     get_locale_list,
                                                     &json));
  std::unique_ptr<base::Value> value_ptr = base::JSONReader::Read(json);
  const base::ListValue* locales = NULL;
  ASSERT_TRUE(value_ptr);
  ASSERT_TRUE(value_ptr->GetAsList(&locales));
  EXPECT_LT(arraysize(kRecommendedLocales1), locales->GetSize());

  // Verify that the list starts with the recommended locales, in correct order.
  for (size_t i = 0; i < arraysize(kRecommendedLocales1); ++i) {
    std::string locale;
    EXPECT_TRUE(locales->GetString(i, &locale));
    EXPECT_EQ(kRecommendedLocales1[i], locale);
  }

  // Verify that the recommended locales do not appear again in the remainder of
  // the list.
  std::set<std::string> recommended_locales;
  for (size_t i = 0; i < arraysize(kRecommendedLocales1); ++i)
    recommended_locales.insert(kRecommendedLocales1[i]);
  for (size_t i = arraysize(kRecommendedLocales1); i < locales->GetSize();
       ++i) {
    std::string locale;
    EXPECT_TRUE(locales->GetString(i, &locale));
    EXPECT_EQ(recommended_locales.end(), recommended_locales.find(locale));
  }

  // Verify that the first recommended locale is selected.
  const std::string get_selected_locale = base::StringPrintf(
      "domAutomationController.send(document.getElementById('pod-row')"
      "    .getPodWithUsername_('%s').querySelector('.language-select')"
      "        .value);",
      account_id_1_.Serialize().c_str());
  std::string selected_locale;
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(contents_,
                                                     get_selected_locale,
                                                     &selected_locale));
  EXPECT_EQ(kRecommendedLocales1[0], selected_locale);

  // Change the list of recommended locales.
  SetRecommendedLocales(kRecommendedLocales2, arraysize(kRecommendedLocales2));

  // Also change the display name as it is easy to ensure that policy has been
  // updated by waiting for a display name change.
  device_local_account_policy_.payload().mutable_userdisplayname()->set_value(
      kDisplayName2);
  UploadAndInstallDeviceLocalAccountPolicy();
  policy::BrowserPolicyConnectorChromeOS* connector =
      g_browser_process->platform_part()->browser_policy_connector_chromeos();
  DeviceLocalAccountPolicyBroker* broker =
      connector->GetDeviceLocalAccountPolicyService()->GetBrokerForUser(
          account_id_1_.GetUserEmail());
  ASSERT_TRUE(broker);
  broker->core()->store()->Load();
  WaitForDisplayName(account_id_1_.GetUserEmail(), kDisplayName2);

  // Verify that the new list of locales is shown in the UI.
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(contents_,
                                                     get_locale_list,
                                                     &json));
  value_ptr = base::JSONReader::Read(json);
  locales = NULL;
  ASSERT_TRUE(value_ptr);
  ASSERT_TRUE(value_ptr->GetAsList(&locales));
  EXPECT_LT(arraysize(kRecommendedLocales2), locales->GetSize());
  for (size_t i = 0; i < arraysize(kRecommendedLocales2); ++i) {
    std::string locale;
    EXPECT_TRUE(locales->GetString(i, &locale));
    EXPECT_EQ(kRecommendedLocales2[i], locale);
  }

  // Verify that the first new recommended locale is selected.
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(contents_,
                                                     get_selected_locale,
                                                     &selected_locale));
  EXPECT_EQ(kRecommendedLocales2[0], selected_locale);

  // Manually select a different locale.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "var languageSelect = document.getElementById('pod-row')"
          "    .getPodWithUsername_('%s').querySelector('.language-select');"
          "languageSelect.value = '%s';"
          "var event = document.createEvent('HTMLEvents');"
          "event.initEvent('change', false, true);"
          "languageSelect.dispatchEvent(event);",
          account_id_1_.Serialize().c_str(), kPublicSessionLocale)));

  // Change the list of recommended locales.
  SetRecommendedLocales(kRecommendedLocales2, arraysize(kRecommendedLocales2));
  device_local_account_policy_.payload().mutable_userdisplayname()->set_value(
      kDisplayName1);
  UploadAndInstallDeviceLocalAccountPolicy();
  broker->core()->store()->Load();
  WaitForDisplayName(account_id_1_.GetUserEmail(), kDisplayName1);

  // Verify that the manually selected locale is still selected.
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(contents_,
                                                     get_selected_locale,
                                                     &selected_locale));
  EXPECT_EQ(kPublicSessionLocale, selected_locale);

  // The UI will request an updated list of keyboard layouts at this point. Wait
  // for the constructions of this list to finish.
  WaitForGetKeyboardLayoutsForLocaleToFinish();

  // Manually select a different keyboard layout.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "document.getElementById('pod-row').getPodWithUsername_('%s')"
          "    .querySelector('.keyboard-select').value = '%s';",
          account_id_1_.Serialize().c_str(),
          public_session_input_method_id_.c_str())));

  // Click on a different pod, causing focus to shift away and the pod to
  // contract.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "document.getElementById('pod-row').getPodWithUsername_('%s')"
          "    .click();",
          account_id_2_.Serialize().c_str())));

  // Click on the pod again, causing it to expand again. Verify that the pod has
  // kept all its state (the advanced form is being shown, the manually selected
  // locale and keyboard layout are selected).
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
      contents_,
      base::StringPrintf(
          "var pod ="
          "    document.getElementById('pod-row').getPodWithUsername_('%s');"
          "pod.click();"
          "var state = {};"
          "state.advanced = pod.classList.contains('advanced');"
          "state.locale = pod.querySelector('.language-select').value;"
          "state.keyboardLayout = pod.querySelector('.keyboard-select').value;"
          "console.log(JSON.stringify(state));"
          "domAutomationController.send(JSON.stringify(state));",
          account_id_1_.Serialize().c_str()),
      &json));
  LOG(ERROR) << json;
  value_ptr = base::JSONReader::Read(json);
  const base::DictionaryValue* state = NULL;
  ASSERT_TRUE(value_ptr);
  ASSERT_TRUE(value_ptr->GetAsDictionary(&state));
  bool advanced = false;
  EXPECT_TRUE(state->GetBoolean("advanced", &advanced));
  EXPECT_TRUE(advanced);
  EXPECT_TRUE(state->GetString("locale", &selected_locale));
  EXPECT_EQ(kPublicSessionLocale, selected_locale);
  std::string selected_keyboard_layout;
  EXPECT_TRUE(state->GetString("keyboardLayout", &selected_keyboard_layout));
  EXPECT_EQ(public_session_input_method_id_, selected_keyboard_layout);

  // Click the enter button to start the session.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "document.getElementById('pod-row').getPodWithUsername_('%s')"
          "    .querySelector('.enter-button').click();",
          account_id_1_.Serialize().c_str())));

  WaitForSessionStart();

  // Verify that the locale and keyboard layout have been applied.
  EXPECT_EQ(kPublicSessionLocale, g_browser_process->GetApplicationLocale());
  EXPECT_EQ(l10n_util::GetLanguage(kPublicSessionLocale),
            icu::Locale::getDefault().getLanguage());
  EXPECT_EQ(public_session_input_method_id_,
            chromeos::input_method::InputMethodManager::Get()
                ->GetActiveIMEState()
                ->GetCurrentInputMethod()
                .id());
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, InvalidRecommendedLocale) {
  // Specify an invalid recommended locale.
  SetRecommendedLocales(kInvalidRecommendedLocale,
                        arraysize(kInvalidRecommendedLocale));
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  // Click on the pod to expand it. Verify that the pod expands to its basic
  // form as there is only one recommended locale.
  bool advanced = false;
  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
      contents_,
      base::StringPrintf(
          "var pod ="
          "    document.getElementById('pod-row').getPodWithUsername_('%s');"
          "pod.click();"
          "domAutomationController.send(pod.classList.contains('advanced'));",
          account_id_1_.Serialize().c_str()),
      &advanced));
  EXPECT_FALSE(advanced);
  EXPECT_EQ(l10n_util::GetLanguage(initial_locale_),
            icu::Locale::getDefault().getLanguage());

  // Click the enter button to start the session.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "document.getElementById('pod-row').getPodWithUsername_('%s')"
          "    .querySelector('.enter-button').click();",
          account_id_1_.Serialize().c_str())));

  WaitForSessionStart();

  // Verify that since the recommended locale was invalid, the locale has not
  // changed and the first keyboard layout applicable to the locale was chosen.
  EXPECT_EQ(initial_locale_, g_browser_process->GetApplicationLocale());
  EXPECT_EQ(l10n_util::GetLanguage(initial_locale_),
            icu::Locale::getDefault().getLanguage());
  VerifyKeyboardLayoutMatchesLocale();
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, LocaleWithIME) {
  // Specify a locale that has real IMEs in addition to a keyboard layout one.
  const char* const kSingleLocaleWithIME[] = {"ja"};
  RunWithRecommendedLocale(kSingleLocaleWithIME,
                           arraysize(kSingleLocaleWithIME));

  EXPECT_GT(chromeos::input_method::InputMethodManager::Get()
                ->GetActiveIMEState()
                ->GetNumActiveInputMethods(),
            1u);
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, LocaleWithNoIME) {
  // Specify a locale that has only keyboard layout.
  const char* const kSingleLocaleWithNoIME[] = {"de"};
  RunWithRecommendedLocale(kSingleLocaleWithNoIME,
                           arraysize(kSingleLocaleWithNoIME));

  EXPECT_EQ(1u, chromeos::input_method::InputMethodManager::Get()
                    ->GetActiveIMEState()
                    ->GetNumActiveInputMethods());
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest,
                       AutoLoginWithoutRecommendedLocales) {
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);
  EnableAutoLogin();

  WaitForPolicy();

  WaitForSessionStart();

  // Verify that the locale has not changed and the first keyboard layout
  // applicable to the locale was chosen.
  EXPECT_EQ(initial_locale_, g_browser_process->GetApplicationLocale());
  EXPECT_EQ(initial_language_, icu::Locale::getDefault().getLanguage());
  VerifyKeyboardLayoutMatchesLocale();
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest,
                       AutoLoginWithRecommendedLocales) {
  // Specify recommended locales.
  SetRecommendedLocales(kRecommendedLocales1, arraysize(kRecommendedLocales1));
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);
  EnableAutoLogin();

  WaitForPolicy();

  WaitForSessionStart();

  // Verify that the first recommended locale has been applied and the first
  // keyboard layout applicable to the locale was chosen.
  EXPECT_EQ(kRecommendedLocales1[0], g_browser_process->GetApplicationLocale());
  EXPECT_EQ(l10n_util::GetLanguage(kRecommendedLocales1[0]),
            icu::Locale::getDefault().getLanguage());
  VerifyKeyboardLayoutMatchesLocale();
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, TermsOfServiceWithLocaleSwitch) {
  // Specify Terms of Service URL.
  ASSERT_TRUE(embedded_test_server()->Start());
  device_local_account_policy_.payload().mutable_termsofserviceurl()->set_value(
      embedded_test_server()->GetURL(
          std::string("/") + kExistentTermsOfServicePath).spec());
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  // Select a different locale.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "var languageSelect = document.getElementById('pod-row')"
          "    .getPodWithUsername_('%s').querySelector('.language-select');"
          "languageSelect.value = '%s';"
          "var event = document.createEvent('HTMLEvents');"
          "event.initEvent('change', false, true);"
          "languageSelect.dispatchEvent(event);",
          account_id_1_.Serialize().c_str(), kPublicSessionLocale)));

  // The UI will have requested an updated list of keyboard layouts at this
  // point. Wait for the constructions of this list to finish.
  WaitForGetKeyboardLayoutsForLocaleToFinish();

  // Set up an observer that will quit the message loop when login has succeeded
  // and the first wizard screen, if any, is being shown.
  base::RunLoop login_wait_run_loop;
  chromeos::MockAuthStatusConsumer login_status_consumer;
  EXPECT_CALL(login_status_consumer, OnAuthSuccess(_)).Times(1).WillOnce(
      InvokeWithoutArgs(&login_wait_run_loop, &base::RunLoop::Quit));
  chromeos::ExistingUserController* controller =
      chromeos::ExistingUserController::current_controller();
  ASSERT_TRUE(controller);
  controller->set_login_status_consumer(&login_status_consumer);

  // Manually select a different keyboard layout and click the enter button to
  // start the session.
  ASSERT_TRUE(content::ExecuteScript(
      contents_,
      base::StringPrintf(
          "var pod ="
          "    document.getElementById('pod-row').getPodWithUsername_('%s');"
          "pod.querySelector('.keyboard-select').value = '%s';"
          "pod.querySelector('.enter-button').click();",
          account_id_1_.Serialize().c_str(),
          public_session_input_method_id_.c_str())));

  // Spin the loop until the login observer fires. Then, unregister the
  // observer.
  login_wait_run_loop.Run();
  controller->set_login_status_consumer(NULL);

  // Verify that the Terms of Service screen is being shown.
  chromeos::WizardController* wizard_controller =
        chromeos::WizardController::default_controller();
  ASSERT_TRUE(wizard_controller);
  ASSERT_TRUE(wizard_controller->current_screen());
  EXPECT_EQ(chromeos::WizardController::kTermsOfServiceScreenName,
            wizard_controller->current_screen()->GetName());

  // Wait for the Terms of Service to finish downloading.
  bool done = false;
  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(contents_,
      "var screenElement = document.getElementById('terms-of-service');"
      "function SendReplyIfDownloadDone() {"
      "  if (screenElement.classList.contains('tos-loading'))"
      "    return false;"
      "  domAutomationController.send(true);"
      "  observer.disconnect();"
      "  return true;"
      "}"
      "var observer = new MutationObserver(SendReplyIfDownloadDone);"
      "if (!SendReplyIfDownloadDone()) {"
      "  var options = { attributes: true, attributeFilter: [ 'class' ] };"
      "  observer.observe(screenElement, options);"
      "}",
      &done));

  // Verify that the locale and keyboard layout have been applied.
  EXPECT_EQ(kPublicSessionLocale, g_browser_process->GetApplicationLocale());
  EXPECT_EQ(l10n_util::GetLanguage(kPublicSessionLocale),
            icu::Locale::getDefault().getLanguage());
  EXPECT_EQ(public_session_input_method_id_,
            chromeos::input_method::InputMethodManager::Get()
                ->GetActiveIMEState()
                ->GetCurrentInputMethod()
                .id());

  // Wait for 'tos-accept-button' to become enabled.
  done = false;
  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
      contents_,
      "var screenElement = document.getElementById('tos-accept-button');"
      "function SendReplyIfAcceptEnabled() {"
      "  if ($('tos-accept-button').disabled)"
      "    return false;"
      "  domAutomationController.send(true);"
      "  observer.disconnect();"
      "  return true;"
      "}"
      "var observer = new MutationObserver(SendReplyIfAcceptEnabled);"
      "if (!SendReplyIfAcceptEnabled()) {"
      "  var options = { attributes: true };"
      "  observer.observe(screenElement, options);"
      "}",
      &done));

  // Click the accept button.
  ASSERT_TRUE(content::ExecuteScript(contents_,
                                     "$('tos-accept-button').click();"));

  WaitForSessionStart();

  // Verify that the locale and keyboard layout are still in force.
  EXPECT_EQ(kPublicSessionLocale, g_browser_process->GetApplicationLocale());
  EXPECT_EQ(l10n_util::GetLanguage(kPublicSessionLocale),
            icu::Locale::getDefault().getLanguage());
  EXPECT_EQ(public_session_input_method_id_,
            chromeos::input_method::InputMethodManager::Get()
                ->GetActiveIMEState()
                ->GetCurrentInputMethod()
                .id());
}

IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, PolicyForExtensions) {
  // Set up a test update server for the Show Managed Storage app.
  ASSERT_TRUE(embedded_test_server()->Start());
  scoped_refptr<TestingUpdateManifestProvider> testing_update_manifest_provider(
      new TestingUpdateManifestProvider(kRelativeUpdateURL));
  testing_update_manifest_provider->AddUpdate(
      kShowManagedStorageID, kShowManagedStorageVersion,
      embedded_test_server()->GetURL(std::string("/") +
                                     kShowManagedStorageCRXPath));
  embedded_test_server()->RegisterRequestHandler(
      base::Bind(&TestingUpdateManifestProvider::HandleRequest,
                 testing_update_manifest_provider));

  // Force-install the Show Managed Storage app. This app can be installed in
  // public sessions because it's whitelisted for testing purposes.
  em::StringList* forcelist = device_local_account_policy_.payload()
      .mutable_extensioninstallforcelist()->mutable_value();
  forcelist->add_entries(base::StringPrintf(
      "%s;%s",
      kShowManagedStorageID,
      embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str()));

  // Set a policy for the app at the policy testserver.
  // Note that the policy for the device-local account will be fetched before
  // the session is started, so the policy for the app must be installed before
  // the first device policy fetch.
  ASSERT_TRUE(test_server_.UpdatePolicyData(
      dm_protocol::kChromeExtensionPolicyType, kShowManagedStorageID,
      "{"
      "  \"string\": {"
      "    \"Value\": \"policy test value one\""
      "  }"
      "}"));

  // Install and refresh the device policy now. This will also fetch the initial
  // user policy for the device-local account now.
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);
  WaitForPolicy();

  // Observe the app installation after login.
  ExtensionInstallObserver install_observer(kShowManagedStorageID);
  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));
  WaitForSessionStart();
  install_observer.Wait();

  // Verify that the app was installed.
  Profile* profile = GetProfileForTest();
  ASSERT_TRUE(profile);
  ExtensionService* extension_service =
      extensions::ExtensionSystem::Get(profile)->extension_service();
  EXPECT_TRUE(extension_service->GetExtensionById(kShowManagedStorageID, true));

  // Wait for the app policy if it hasn't been fetched yet.
  ProfilePolicyConnector* connector =
      ProfilePolicyConnectorFactory::GetForBrowserContext(profile);
  ASSERT_TRUE(connector);
  PolicyService* policy_service = connector->policy_service();
  ASSERT_TRUE(policy_service);
  const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, kShowManagedStorageID);
  if (policy_service->GetPolicies(ns).empty()) {
    PolicyChangeRegistrar policy_registrar(policy_service, ns);
    base::RunLoop run_loop;
    policy_registrar.Observe(
        "string", base::Bind(&PolicyChangedCallback, run_loop.QuitClosure()));
    run_loop.Run();
  }

  // Verify that the app policy was set.
  base::StringValue expected_value("policy test value one");
  EXPECT_TRUE(base::Value::Equals(
      &expected_value,
      policy_service->GetPolicies(ns).GetValue("string")));

  // Now update the policy at the server.
  ASSERT_TRUE(test_server_.UpdatePolicyData(
      dm_protocol::kChromeExtensionPolicyType, kShowManagedStorageID,
      "{"
      "  \"string\": {"
      "    \"Value\": \"policy test value two\""
      "  }"
      "}"));

  // And issue a policy refresh.
  {
    PolicyChangeRegistrar policy_registrar(policy_service, ns);
    base::RunLoop run_loop;
    policy_registrar.Observe(
        "string", base::Bind(&PolicyChangedCallback, run_loop.QuitClosure()));
    policy_service->RefreshPolicies(base::Closure());
    run_loop.Run();
  }

  // Verify that the app policy was updated.
  base::StringValue expected_new_value("policy test value two");
  EXPECT_TRUE(base::Value::Equals(
      &expected_new_value,
      policy_service->GetPolicies(ns).GetValue("string")));
}

class TermsOfServiceDownloadTest : public DeviceLocalAccountTest,
                                   public testing::WithParamInterface<bool> {
};

IN_PROC_BROWSER_TEST_P(TermsOfServiceDownloadTest, TermsOfServiceScreen) {
  // Specify Terms of Service URL.
  ASSERT_TRUE(embedded_test_server()->Start());
  device_local_account_policy_.payload().mutable_termsofserviceurl()->set_value(
      embedded_test_server()->GetURL(
            std::string("/") +
                (GetParam() ? kExistentTermsOfServicePath
                            : kNonexistentTermsOfServicePath)).spec());
  UploadAndInstallDeviceLocalAccountPolicy();
  AddPublicSessionToDevicePolicy(kAccountId1);

  WaitForPolicy();

  ASSERT_NO_FATAL_FAILURE(StartLogin(std::string(), std::string()));

  // Set up an observer that will quit the message loop when login has succeeded
  // and the first wizard screen, if any, is being shown.
  base::RunLoop login_wait_run_loop;
  chromeos::MockAuthStatusConsumer login_status_consumer;
  EXPECT_CALL(login_status_consumer, OnAuthSuccess(_)).Times(1).WillOnce(
      InvokeWithoutArgs(&login_wait_run_loop, &base::RunLoop::Quit));

  // Spin the loop until the observer fires. Then, unregister the observer.
  chromeos::ExistingUserController* controller =
      chromeos::ExistingUserController::current_controller();
  ASSERT_TRUE(controller);
  controller->set_login_status_consumer(&login_status_consumer);
  login_wait_run_loop.Run();
  controller->set_login_status_consumer(NULL);

  // Verify that the Terms of Service screen is being shown.
  chromeos::WizardController* wizard_controller =
        chromeos::WizardController::default_controller();
  ASSERT_TRUE(wizard_controller);
  ASSERT_TRUE(wizard_controller->current_screen());
  EXPECT_EQ(chromeos::WizardController::kTermsOfServiceScreenName,
            wizard_controller->current_screen()->GetName());

  // Wait for the Terms of Service to finish downloading, then get the status of
  // the screen's UI elements.
  std::string json;
  ASSERT_TRUE(content::ExecuteScriptAndExtractString(contents_,
      "var screenElement = document.getElementById('terms-of-service');"
      "function SendReplyIfDownloadDone() {"
      "  if (screenElement.classList.contains('tos-loading'))"
      "    return false;"
      "  var status = {};"
      "  status.heading = document.getElementById('tos-heading').textContent;"
      "  status.subheading ="
      "      document.getElementById('tos-subheading').textContent;"
      "  status.contentHeading ="
      "      document.getElementById('tos-content-heading').textContent;"
      "  status.content ="
      "      document.getElementById('tos-content-main').textContent;"
      "  status.error = screenElement.classList.contains('error');"
      "  status.acceptEnabled ="
      "      !document.getElementById('tos-accept-button').disabled;"
      "  domAutomationController.send(JSON.stringify(status));"
      "  observer.disconnect();"
      "  return true;"
      "}"
      "var observer = new MutationObserver(SendReplyIfDownloadDone);"
      "if (!SendReplyIfDownloadDone()) {"
      "  var options = { attributes: true, attributeFilter: [ 'class' ] };"
      "  observer.observe(screenElement, options);"
      "}",
      &json));
  std::unique_ptr<base::Value> value_ptr = base::JSONReader::Read(json);
  const base::DictionaryValue* status = NULL;
  ASSERT_TRUE(value_ptr);
  ASSERT_TRUE(value_ptr->GetAsDictionary(&status));
  std::string heading;
  EXPECT_TRUE(status->GetString("heading", &heading));
  std::string subheading;
  EXPECT_TRUE(status->GetString("subheading", &subheading));
  std::string content_heading;
  EXPECT_TRUE(status->GetString("contentHeading", &content_heading));
  std::string content;
  EXPECT_TRUE(status->GetString("content", &content));
  bool error;
  EXPECT_TRUE(status->GetBoolean("error", &error));
  bool accept_enabled;
  EXPECT_TRUE(status->GetBoolean("acceptEnabled", &accept_enabled));

  // Verify that the screen's headings have been set correctly.
  EXPECT_EQ(
      l10n_util::GetStringFUTF8(IDS_TERMS_OF_SERVICE_SCREEN_HEADING,
                                base::UTF8ToUTF16(kDomain)),
      heading);
  EXPECT_EQ(
      l10n_util::GetStringFUTF8(IDS_TERMS_OF_SERVICE_SCREEN_SUBHEADING,
                                base::UTF8ToUTF16(kDomain)),
      subheading);
  EXPECT_EQ(
      l10n_util::GetStringFUTF8(IDS_TERMS_OF_SERVICE_SCREEN_CONTENT_HEADING,
                                base::UTF8ToUTF16(kDomain)),
      content_heading);

  if (!GetParam()) {
    // The Terms of Service URL was invalid. Verify that the screen is showing
    // an error and the accept button is disabled.
    EXPECT_TRUE(error);
    EXPECT_FALSE(accept_enabled);
    return;
  }

  // The Terms of Service URL was valid. Verify that the screen is showing the
  // downloaded Terms of Service and the accept button is enabled.
  base::FilePath test_dir;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  std::string terms_of_service;
  ASSERT_TRUE(base::ReadFileToString(
      test_dir.Append(kExistentTermsOfServicePath), &terms_of_service));
  EXPECT_EQ(terms_of_service, content);
  EXPECT_FALSE(error);
  EXPECT_TRUE(accept_enabled);

  // Click the accept button.
  ASSERT_TRUE(content::ExecuteScript(contents_,
                                     "$('tos-accept-button').click();"));

  WaitForSessionStart();
}

INSTANTIATE_TEST_CASE_P(TermsOfServiceDownloadTestInstance,
                        TermsOfServiceDownloadTest, testing::Bool());

}  // namespace policy
