Move policy code into components/policy.

The code moved to component/policy can be included on iOS, but can't depend
on chrome/. This move includes an exception for the policy protobufs:

- the generic cloud policy protobufs will be moved into a new repository,
  due to a dependency from a ChromeOS package

- the user cloud policy protobuf and its decoding will move into a new component
  for user policy

BUG=271392
TBR=jochen@chromium.org

Review URL: https://codereview.chromium.org/109743002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@239399 0039d316-1c4b-4281-b951-d872f2087c98


CrOS-Libchrome-Original-Commit: 76b4b15804dafa5e586738bd7c8ac12fea6ddec2
diff --git a/components/policy/core/common/config_dir_policy_loader.cc b/components/policy/core/common/config_dir_policy_loader.cc
new file mode 100644
index 0000000..b8bd66a
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader.cc
@@ -0,0 +1,232 @@
+// Copyright (c) 2012 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 "components/policy/core/common/config_dir_policy_loader.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/platform_file.h"
+#include "base/stl_util.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_load_status.h"
+
+namespace policy {
+
+namespace {
+
+// Subdirectories that contain the mandatory and recommended policies.
+const base::FilePath::CharType kMandatoryConfigDir[] =
+    FILE_PATH_LITERAL("managed");
+const base::FilePath::CharType kRecommendedConfigDir[] =
+    FILE_PATH_LITERAL("recommended");
+
+PolicyLoadStatus JsonErrorToPolicyLoadStatus(int status) {
+  switch (status) {
+    case JSONFileValueSerializer::JSON_ACCESS_DENIED:
+    case JSONFileValueSerializer::JSON_CANNOT_READ_FILE:
+    case JSONFileValueSerializer::JSON_FILE_LOCKED:
+      return POLICY_LOAD_STATUS_READ_ERROR;
+    case JSONFileValueSerializer::JSON_NO_SUCH_FILE:
+      return POLICY_LOAD_STATUS_MISSING;
+    case base::JSONReader::JSON_INVALID_ESCAPE:
+    case base::JSONReader::JSON_SYNTAX_ERROR:
+    case base::JSONReader::JSON_UNEXPECTED_TOKEN:
+    case base::JSONReader::JSON_TRAILING_COMMA:
+    case base::JSONReader::JSON_TOO_MUCH_NESTING:
+    case base::JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT:
+    case base::JSONReader::JSON_UNSUPPORTED_ENCODING:
+    case base::JSONReader::JSON_UNQUOTED_DICTIONARY_KEY:
+      return POLICY_LOAD_STATUS_PARSE_ERROR;
+    case base::JSONReader::JSON_NO_ERROR:
+      NOTREACHED();
+      return POLICY_LOAD_STATUS_STARTED;
+  }
+  NOTREACHED() << "Invalid status " << status;
+  return POLICY_LOAD_STATUS_PARSE_ERROR;
+}
+
+}  // namespace
+
+ConfigDirPolicyLoader::ConfigDirPolicyLoader(
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
+    const base::FilePath& config_dir,
+    PolicyScope scope)
+    : AsyncPolicyLoader(task_runner), config_dir_(config_dir), scope_(scope) {}
+
+ConfigDirPolicyLoader::~ConfigDirPolicyLoader() {}
+
+void ConfigDirPolicyLoader::InitOnBackgroundThread() {
+  base::FilePathWatcher::Callback callback =
+      base::Bind(&ConfigDirPolicyLoader::OnFileUpdated, base::Unretained(this));
+  mandatory_watcher_.Watch(config_dir_.Append(kMandatoryConfigDir), false,
+                           callback);
+  recommended_watcher_.Watch(config_dir_.Append(kRecommendedConfigDir), false,
+                             callback);
+}
+
+scoped_ptr<PolicyBundle> ConfigDirPolicyLoader::Load() {
+  scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
+  LoadFromPath(config_dir_.Append(kMandatoryConfigDir),
+               POLICY_LEVEL_MANDATORY,
+               bundle.get());
+  LoadFromPath(config_dir_.Append(kRecommendedConfigDir),
+               POLICY_LEVEL_RECOMMENDED,
+               bundle.get());
+  return bundle.Pass();
+}
+
+base::Time ConfigDirPolicyLoader::LastModificationTime() {
+  static const base::FilePath::CharType* kConfigDirSuffixes[] = {
+    kMandatoryConfigDir,
+    kRecommendedConfigDir,
+  };
+
+  base::Time last_modification = base::Time();
+  base::PlatformFileInfo info;
+
+  for (size_t i = 0; i < arraysize(kConfigDirSuffixes); ++i) {
+    base::FilePath path(config_dir_.Append(kConfigDirSuffixes[i]));
+
+    // Skip if the file doesn't exist, or it isn't a directory.
+    if (!base::GetFileInfo(path, &info) || !info.is_directory)
+      continue;
+
+    // Enumerate the files and find the most recent modification timestamp.
+    base::FileEnumerator file_enumerator(path, false,
+                                         base::FileEnumerator::FILES);
+    for (base::FilePath config_file = file_enumerator.Next();
+         !config_file.empty();
+         config_file = file_enumerator.Next()) {
+      if (base::GetFileInfo(config_file, &info) && !info.is_directory)
+        last_modification = std::max(last_modification, info.last_modified);
+    }
+  }
+
+  return last_modification;
+}
+
+void ConfigDirPolicyLoader::LoadFromPath(const base::FilePath& path,
+                                         PolicyLevel level,
+                                         PolicyBundle* bundle) {
+  // Enumerate the files and sort them lexicographically.
+  std::set<base::FilePath> files;
+  base::FileEnumerator file_enumerator(path, false,
+                                       base::FileEnumerator::FILES);
+  for (base::FilePath config_file_path = file_enumerator.Next();
+       !config_file_path.empty(); config_file_path = file_enumerator.Next())
+    files.insert(config_file_path);
+
+  PolicyLoadStatusSample status;
+  if (files.empty()) {
+    status.Add(POLICY_LOAD_STATUS_NO_POLICY);
+    return;
+  }
+
+  // Start with an empty dictionary and merge the files' contents.
+  // The files are processed in reverse order because |MergeFrom| gives priority
+  // to existing keys, but the ConfigDirPolicyProvider gives priority to the
+  // last file in lexicographic order.
+  for (std::set<base::FilePath>::reverse_iterator config_file_iter =
+           files.rbegin(); config_file_iter != files.rend();
+       ++config_file_iter) {
+    JSONFileValueSerializer deserializer(*config_file_iter);
+    deserializer.set_allow_trailing_comma(true);
+    int error_code = 0;
+    std::string error_msg;
+    scoped_ptr<base::Value> value(
+        deserializer.Deserialize(&error_code, &error_msg));
+    if (!value.get()) {
+      LOG(WARNING) << "Failed to read configuration file "
+                   << config_file_iter->value() << ": " << error_msg;
+      status.Add(JsonErrorToPolicyLoadStatus(error_code));
+      continue;
+    }
+    base::DictionaryValue* dictionary_value = NULL;
+    if (!value->GetAsDictionary(&dictionary_value)) {
+      LOG(WARNING) << "Expected JSON dictionary in configuration file "
+                   << config_file_iter->value();
+      status.Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+      continue;
+    }
+
+    // Detach the "3rdparty" node.
+    scoped_ptr<base::Value> third_party;
+    if (dictionary_value->Remove("3rdparty", &third_party))
+      Merge3rdPartyPolicy(third_party.get(), level, bundle);
+
+    // Add chrome policy.
+    PolicyMap policy_map;
+    policy_map.LoadFrom(dictionary_value, level, scope_);
+    bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+        .MergeFrom(policy_map);
+  }
+}
+
+void ConfigDirPolicyLoader::Merge3rdPartyPolicy(
+    const base::Value* policies,
+    PolicyLevel level,
+    PolicyBundle* bundle) {
+  // The first-level entries in |policies| are PolicyDomains. The second-level
+  // entries are component IDs, and the third-level entries are the policies
+  // for that domain/component namespace.
+
+  const base::DictionaryValue* domains_dictionary;
+  if (!policies->GetAsDictionary(&domains_dictionary)) {
+    LOG(WARNING) << "3rdparty value is not a dictionary!";
+    return;
+  }
+
+  // Helper to lookup a domain given its string name.
+  std::map<std::string, PolicyDomain> supported_domains;
+  supported_domains["extensions"] = POLICY_DOMAIN_EXTENSIONS;
+
+  for (base::DictionaryValue::Iterator domains_it(*domains_dictionary);
+       !domains_it.IsAtEnd(); domains_it.Advance()) {
+    if (!ContainsKey(supported_domains, domains_it.key())) {
+      LOG(WARNING) << "Unsupported 3rd party policy domain: "
+                   << domains_it.key();
+      continue;
+    }
+
+    const base::DictionaryValue* components_dictionary;
+    if (!domains_it.value().GetAsDictionary(&components_dictionary)) {
+      LOG(WARNING) << "3rdparty/" << domains_it.key()
+                   << " value is not a dictionary!";
+      continue;
+    }
+
+    PolicyDomain domain = supported_domains[domains_it.key()];
+    for (base::DictionaryValue::Iterator components_it(*components_dictionary);
+         !components_it.IsAtEnd(); components_it.Advance()) {
+      const base::DictionaryValue* policy_dictionary;
+      if (!components_it.value().GetAsDictionary(&policy_dictionary)) {
+        LOG(WARNING) << "3rdparty/" << domains_it.key() << "/"
+                     << components_it.key() << " value is not a dictionary!";
+        continue;
+      }
+
+      PolicyMap policy;
+      policy.LoadFrom(policy_dictionary, level, scope_);
+      bundle->Get(PolicyNamespace(domain, components_it.key()))
+          .MergeFrom(policy);
+    }
+  }
+}
+
+void ConfigDirPolicyLoader::OnFileUpdated(const base::FilePath& path,
+                                          bool error) {
+  if (!error)
+    Reload(false);
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/config_dir_policy_loader.h b/components/policy/core/common/config_dir_policy_loader.h
new file mode 100644
index 0000000..054844b
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
+
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class Value;
+}
+
+namespace policy {
+
+// A policy loader implementation backed by a set of files in a given
+// directory. The files should contain JSON-formatted policy settings. They are
+// merged together and the result is returned in a PolicyBundle.
+// The files are consulted in lexicographic file name order, so the
+// last value read takes precedence in case of policy key collisions.
+class POLICY_EXPORT ConfigDirPolicyLoader : public AsyncPolicyLoader {
+ public:
+  ConfigDirPolicyLoader(scoped_refptr<base::SequencedTaskRunner> task_runner,
+                        const base::FilePath& config_dir,
+                        PolicyScope scope);
+  virtual ~ConfigDirPolicyLoader();
+
+  // AsyncPolicyLoader implementation.
+  virtual void InitOnBackgroundThread() OVERRIDE;
+  virtual scoped_ptr<PolicyBundle> Load() OVERRIDE;
+  virtual base::Time LastModificationTime() OVERRIDE;
+
+ private:
+  // Loads the policy files at |path| into the |bundle|, with the given |level|.
+  void LoadFromPath(const base::FilePath& path,
+                    PolicyLevel level,
+                    PolicyBundle* bundle);
+
+  // Merges the 3rd party |policies| into the |bundle|, with the given |level|.
+  void Merge3rdPartyPolicy(const base::Value* policies,
+                           PolicyLevel level,
+                           PolicyBundle* bundle);
+
+  // Callback for the FilePathWatchers.
+  void OnFileUpdated(const base::FilePath& path, bool error);
+
+  // The directory containing the policy files.
+  base::FilePath config_dir_;
+
+  // Policies loaded by this provider will have this scope.
+  PolicyScope scope_;
+
+  // Watchers for events on the mandatory and recommended subdirectories of
+  // |config_dir_|.
+  base::FilePathWatcher mandatory_watcher_;
+  base::FilePathWatcher recommended_watcher_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConfigDirPolicyLoader);
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
diff --git a/components/policy/core/common/config_dir_policy_loader_unittest.cc b/components/policy/core/common/config_dir_policy_loader_unittest.cc
new file mode 100644
index 0000000..de74964
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader_unittest.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 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 "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/common/async_policy_provider.h"
+#include "components/policy/core/common/config_dir_policy_loader.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+namespace {
+
+// Subdirectory of the config dir that contains mandatory policies.
+const base::FilePath::CharType kMandatoryPath[] = FILE_PATH_LITERAL("managed");
+
+class TestHarness : public PolicyProviderTestHarness {
+ public:
+  TestHarness();
+  virtual ~TestHarness();
+
+  virtual void SetUp() OVERRIDE;
+
+  virtual ConfigurationPolicyProvider* CreateProvider(
+      SchemaRegistry* registry,
+      scoped_refptr<base::SequencedTaskRunner> task_runner) OVERRIDE;
+
+  virtual void InstallEmptyPolicy() OVERRIDE;
+  virtual void InstallStringPolicy(const std::string& policy_name,
+                                   const std::string& policy_value) OVERRIDE;
+  virtual void InstallIntegerPolicy(const std::string& policy_name,
+                                    int policy_value) OVERRIDE;
+  virtual void InstallBooleanPolicy(const std::string& policy_name,
+                                    bool policy_value) OVERRIDE;
+  virtual void InstallStringListPolicy(
+      const std::string& policy_name,
+      const base::ListValue* policy_value) OVERRIDE;
+  virtual void InstallDictionaryPolicy(
+      const std::string& policy_name,
+      const base::DictionaryValue* policy_value) OVERRIDE;
+  virtual void Install3rdPartyPolicy(
+      const base::DictionaryValue* policies) OVERRIDE;
+
+  const base::FilePath& test_dir() { return test_dir_.path(); }
+
+  // JSON-encode a dictionary and write it to a file.
+  void WriteConfigFile(const base::DictionaryValue& dict,
+                       const std::string& file_name);
+
+  // Returns a unique name for a policy file. Each subsequent call returns a new
+  // name that comes lexicographically after the previous one.
+  std::string NextConfigFileName();
+
+  static PolicyProviderTestHarness* Create();
+
+ private:
+  base::ScopedTempDir test_dir_;
+  int next_policy_file_index_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestHarness);
+};
+
+TestHarness::TestHarness()
+    : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE),
+      next_policy_file_index_(100) {}
+
+TestHarness::~TestHarness() {}
+
+void TestHarness::SetUp() {
+  ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
+}
+
+ConfigurationPolicyProvider* TestHarness::CreateProvider(
+    SchemaRegistry* registry,
+    scoped_refptr<base::SequencedTaskRunner> task_runner) {
+  scoped_ptr<AsyncPolicyLoader> loader(new ConfigDirPolicyLoader(
+      task_runner, test_dir(), POLICY_SCOPE_MACHINE));
+  return new AsyncPolicyProvider(registry, loader.Pass());
+}
+
+void TestHarness::InstallEmptyPolicy() {
+  base::DictionaryValue dict;
+  WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallStringPolicy(const std::string& policy_name,
+                                      const std::string& policy_value) {
+  base::DictionaryValue dict;
+  dict.SetString(policy_name, policy_value);
+  WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallIntegerPolicy(const std::string& policy_name,
+                                       int policy_value) {
+  base::DictionaryValue dict;
+  dict.SetInteger(policy_name, policy_value);
+  WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallBooleanPolicy(const std::string& policy_name,
+                                       bool policy_value) {
+  base::DictionaryValue dict;
+  dict.SetBoolean(policy_name, policy_value);
+  WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallStringListPolicy(const std::string& policy_name,
+                                          const base::ListValue* policy_value) {
+  base::DictionaryValue dict;
+  dict.Set(policy_name, policy_value->DeepCopy());
+  WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallDictionaryPolicy(
+    const std::string& policy_name,
+    const base::DictionaryValue* policy_value) {
+  base::DictionaryValue dict;
+  dict.Set(policy_name, policy_value->DeepCopy());
+  WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::Install3rdPartyPolicy(const base::DictionaryValue* policies) {
+  base::DictionaryValue dict;
+  dict.Set("3rdparty", policies->DeepCopy());
+  WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::WriteConfigFile(const base::DictionaryValue& dict,
+                                  const std::string& file_name) {
+  std::string data;
+  JSONStringValueSerializer serializer(&data);
+  serializer.Serialize(dict);
+  const base::FilePath mandatory_dir(test_dir().Append(kMandatoryPath));
+  ASSERT_TRUE(base::CreateDirectory(mandatory_dir));
+  const base::FilePath file_path(mandatory_dir.AppendASCII(file_name));
+  ASSERT_EQ((int) data.size(),
+            file_util::WriteFile(file_path, data.c_str(), data.size()));
+}
+
+std::string TestHarness::NextConfigFileName() {
+  EXPECT_LE(next_policy_file_index_, 999);
+  return std::string("policy") + base::IntToString(next_policy_file_index_++);
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::Create() {
+  return new TestHarness();
+}
+
+}  // namespace
+
+// Instantiate abstract test case for basic policy reading tests.
+INSTANTIATE_TEST_CASE_P(
+    ConfigDirPolicyLoaderTest,
+    ConfigurationPolicyProviderTest,
+    testing::Values(TestHarness::Create));
+
+// Instantiate abstract test case for 3rd party policy reading tests.
+INSTANTIATE_TEST_CASE_P(
+    ConfigDir3rdPartyPolicyLoaderTest,
+    Configuration3rdPartyPolicyProviderTest,
+    testing::Values(TestHarness::Create));
+
+// Some tests that exercise special functionality in ConfigDirPolicyLoader.
+class ConfigDirPolicyLoaderTest : public PolicyTestBase {
+ protected:
+  virtual void SetUp() OVERRIDE {
+    PolicyTestBase::SetUp();
+    harness_.SetUp();
+  }
+
+  TestHarness harness_;
+};
+
+// The preferences dictionary is expected to be empty when there are no files to
+// load.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsEmpty) {
+  ConfigDirPolicyLoader loader(
+      loop_.message_loop_proxy(), harness_.test_dir(), POLICY_SCOPE_MACHINE);
+  scoped_ptr<PolicyBundle> bundle(loader.Load());
+  ASSERT_TRUE(bundle.get());
+  const PolicyBundle kEmptyBundle;
+  EXPECT_TRUE(bundle->Equals(kEmptyBundle));
+}
+
+// Reading from a non-existent directory should result in an empty preferences
+// dictionary.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsNonExistentDirectory) {
+  base::FilePath non_existent_dir(
+      harness_.test_dir().Append(FILE_PATH_LITERAL("not_there")));
+  ConfigDirPolicyLoader loader(
+      loop_.message_loop_proxy(), non_existent_dir, POLICY_SCOPE_MACHINE);
+  scoped_ptr<PolicyBundle> bundle(loader.Load());
+  ASSERT_TRUE(bundle.get());
+  const PolicyBundle kEmptyBundle;
+  EXPECT_TRUE(bundle->Equals(kEmptyBundle));
+}
+
+// Test merging values from different files.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsMergePrefs) {
+  // Write a bunch of data files in order to increase the chance to detect the
+  // provider not respecting lexicographic ordering when reading them. Since the
+  // filesystem may return files in arbitrary order, there is no way to be sure,
+  // but this is better than nothing.
+  base::DictionaryValue test_dict_bar;
+  test_dict_bar.SetString("HomepageLocation", "http://bar.com");
+  for (unsigned int i = 1; i <= 4; ++i)
+    harness_.WriteConfigFile(test_dict_bar, base::IntToString(i));
+  base::DictionaryValue test_dict_foo;
+  test_dict_foo.SetString("HomepageLocation", "http://foo.com");
+  harness_.WriteConfigFile(test_dict_foo, "9");
+  for (unsigned int i = 5; i <= 8; ++i)
+    harness_.WriteConfigFile(test_dict_bar, base::IntToString(i));
+
+  ConfigDirPolicyLoader loader(
+      loop_.message_loop_proxy(), harness_.test_dir(), POLICY_SCOPE_USER);
+  scoped_ptr<PolicyBundle> bundle(loader.Load());
+  ASSERT_TRUE(bundle.get());
+  PolicyBundle expected_bundle;
+  expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+      .LoadFrom(&test_dict_foo, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
+  EXPECT_TRUE(bundle->Equals(expected_bundle));
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/mock_policy_service.cc b/components/policy/core/common/mock_policy_service.cc
new file mode 100644
index 0000000..6e3e0c1
--- /dev/null
+++ b/components/policy/core/common/mock_policy_service.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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 "components/policy/core/common/mock_policy_service.h"
+
+namespace policy {
+
+MockPolicyServiceObserver::MockPolicyServiceObserver() {
+}
+
+MockPolicyServiceObserver::~MockPolicyServiceObserver() {
+}
+
+MockPolicyService::MockPolicyService() {
+}
+
+MockPolicyService::~MockPolicyService() {
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/mock_policy_service.h b/components/policy/core/common/mock_policy_service.h
new file mode 100644
index 0000000..584f093
--- /dev/null
+++ b/components/policy/core/common/mock_policy_service.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
+
+#include "components/policy/core/common/policy_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class MockPolicyServiceObserver : public PolicyService::Observer {
+ public:
+  MockPolicyServiceObserver();
+  virtual ~MockPolicyServiceObserver();
+
+  MOCK_METHOD3(OnPolicyUpdated, void(const PolicyNamespace&,
+                                     const PolicyMap& previous,
+                                     const PolicyMap& current));
+  MOCK_METHOD1(OnPolicyServiceInitialized, void(PolicyDomain));
+};
+
+class MockPolicyService : public PolicyService {
+ public:
+  MockPolicyService();
+  virtual ~MockPolicyService();
+
+  MOCK_METHOD2(AddObserver, void(PolicyDomain, Observer*));
+  MOCK_METHOD2(RemoveObserver, void(PolicyDomain, Observer*));
+
+  MOCK_CONST_METHOD1(GetPolicies, const PolicyMap&(const PolicyNamespace&));
+  MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
+  MOCK_METHOD1(RefreshPolicies, void(const base::Closure&));
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
diff --git a/components/policy/core/common/policy_load_status.cc b/components/policy/core/common/policy_load_status.cc
new file mode 100644
index 0000000..71c5059
--- /dev/null
+++ b/components/policy/core/common/policy_load_status.cc
@@ -0,0 +1,38 @@
+// 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 "components/policy/core/common/policy_load_status.h"
+
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+namespace {
+
+const char kHistogramName[] = "Enterprise.PolicyLoadStatus";
+
+}  // namespace
+
+PolicyLoadStatusSample::PolicyLoadStatusSample()
+    : histogram_(base::LinearHistogram::FactoryGet(
+          kHistogramName, 1, POLICY_LOAD_STATUS_SIZE,
+          POLICY_LOAD_STATUS_SIZE + 1,
+          base::Histogram::kUmaTargetedHistogramFlag)) {
+  Add(POLICY_LOAD_STATUS_STARTED);
+}
+
+PolicyLoadStatusSample::~PolicyLoadStatusSample() {
+  for (int i = 0; i < POLICY_LOAD_STATUS_SIZE; ++i) {
+    if (status_bits_[i])
+      histogram_->Add(i);
+  }
+}
+
+void PolicyLoadStatusSample::Add(PolicyLoadStatus status) {
+  status_bits_[status] = true;
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_load_status.h b/components/policy/core/common/policy_load_status.h
new file mode 100644
index 0000000..5f4d017
--- /dev/null
+++ b/components/policy/core/common/policy_load_status.h
@@ -0,0 +1,67 @@
+// 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.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOAD_STATUS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOAD_STATUS_H_
+
+#include <bitset>
+
+#include "base/basictypes.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class HistogramBase;
+}
+
+namespace policy {
+
+// UMA histogram enum for policy load status. Don't change existing constants,
+// append additional constants to the end if needed.
+enum PolicyLoadStatus {
+  // Policy load attempt started. This gets logged for each policy load attempt
+  // to get a baseline on the number of requests, and an arbitrary number of
+  // the below status codes may get added in addition.
+  POLICY_LOAD_STATUS_STARTED,
+  // System failed to determine whether there's policy.
+  POLICY_LOAD_STATUS_QUERY_FAILED,
+  // No policy present.
+  POLICY_LOAD_STATUS_NO_POLICY,
+  // Data inaccessible, such as non-local policy file.
+  POLICY_LOAD_STATUS_INACCCESSIBLE,
+  // Data missing, such as policy file not present.
+  POLICY_LOAD_STATUS_MISSING,
+  // Trying with Wow64 redirection disabled.
+  POLICY_LOAD_STATUS_WOW64_REDIRECTION_DISABLED,
+  // Data read error, for example file reading errors.
+  POLICY_LOAD_STATUS_READ_ERROR,
+  // Data too large to process.
+  POLICY_LOAD_STATUS_TOO_BIG,
+  // Parse error.
+  POLICY_LOAD_STATUS_PARSE_ERROR,
+
+  // This must stay last.
+  POLICY_LOAD_STATUS_SIZE
+};
+
+// A helper for generating policy load status UMA statistics that'll collect
+// histogram samples for a policy load operation and records histogram samples
+// for the status codes that were seen on destruction.
+class POLICY_EXPORT PolicyLoadStatusSample {
+ public:
+  PolicyLoadStatusSample();
+  ~PolicyLoadStatusSample();
+
+  // Adds a status code.
+  void Add(PolicyLoadStatus status);
+
+ private:
+  std::bitset<POLICY_LOAD_STATUS_SIZE> status_bits_;
+  base::HistogramBase* histogram_;
+
+  DISALLOW_COPY_AND_ASSIGN(PolicyLoadStatusSample);
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOAD_STATUS_H_
diff --git a/components/policy/core/common/policy_service.cc b/components/policy/core/common/policy_service.cc
new file mode 100644
index 0000000..fe4bb30
--- /dev/null
+++ b/components/policy/core/common/policy_service.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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 "components/policy/core/common/policy_service.h"
+
+#include "base/values.h"
+
+namespace policy {
+
+PolicyChangeRegistrar::PolicyChangeRegistrar(PolicyService* policy_service,
+                                             const PolicyNamespace& ns)
+    : policy_service_(policy_service),
+      ns_(ns) {}
+
+PolicyChangeRegistrar::~PolicyChangeRegistrar() {
+  if (!callback_map_.empty())
+    policy_service_->RemoveObserver(ns_.domain, this);
+}
+
+void PolicyChangeRegistrar::Observe(const std::string& policy_name,
+                                    const UpdateCallback& callback) {
+  if (callback_map_.empty())
+    policy_service_->AddObserver(ns_.domain, this);
+  callback_map_[policy_name] = callback;
+}
+
+void PolicyChangeRegistrar::OnPolicyUpdated(const PolicyNamespace& ns,
+                                            const PolicyMap& previous,
+                                            const PolicyMap& current) {
+  if (ns != ns_)
+    return;
+  for (CallbackMap::iterator it = callback_map_.begin();
+       it != callback_map_.end(); ++it) {
+    const Value* prev = previous.GetValue(it->first);
+    const Value* cur = current.GetValue(it->first);
+    if (!base::Value::Equals(prev, cur))
+      it->second.Run(prev, cur);
+  }
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_service.h b/components/policy/core/common/policy_service.h
new file mode 100644
index 0000000..4293e5c
--- /dev/null
+++ b/components/policy/core/common/policy_service.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 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.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// The PolicyService merges policies from all available sources, taking into
+// account their priorities. Policy clients can retrieve policy for their domain
+// and register for notifications on policy updates.
+//
+// The PolicyService is available from BrowserProcess as a global singleton.
+// There is also a PolicyService for browser-wide policies available from
+// BrowserProcess as a global singleton.
+class POLICY_EXPORT PolicyService {
+ public:
+  class POLICY_EXPORT Observer {
+   public:
+    // Invoked whenever policies for the given |ns| namespace are modified.
+    // This is only invoked for changes that happen after AddObserver is called.
+    // |previous| contains the values of the policies before the update,
+    // and |current| contains the current values.
+    virtual void OnPolicyUpdated(const PolicyNamespace& ns,
+                                 const PolicyMap& previous,
+                                 const PolicyMap& current) = 0;
+
+    // Invoked at most once for each |domain|, when the PolicyService becomes
+    // ready. If IsInitializationComplete() is false, then this will be invoked
+    // once all the policy providers have finished loading their policies for
+    // |domain|.
+    virtual void OnPolicyServiceInitialized(PolicyDomain domain) {}
+
+   protected:
+    virtual ~Observer() {}
+  };
+
+  virtual ~PolicyService() {}
+
+  // Observes changes to all components of the given |domain|.
+  virtual void AddObserver(PolicyDomain domain, Observer* observer) = 0;
+
+  virtual void RemoveObserver(PolicyDomain domain, Observer* observer) = 0;
+
+  virtual const PolicyMap& GetPolicies(const PolicyNamespace& ns) const = 0;
+
+  // The PolicyService loads policy from several sources, and some require
+  // asynchronous loads. IsInitializationComplete() returns true once all
+  // sources have loaded their policies for the given |domain|.
+  // It is safe to read policy from the PolicyService even if
+  // IsInitializationComplete() is false; there will be an OnPolicyUpdated()
+  // notification once new policies become available.
+  //
+  // OnPolicyServiceInitialized() is called when IsInitializationComplete()
+  // becomes true, which happens at most once for each domain.
+  // If IsInitializationComplete() is already true for |domain| when an Observer
+  // is registered, then that Observer will not receive an
+  // OnPolicyServiceInitialized() notification.
+  virtual bool IsInitializationComplete(PolicyDomain domain) const = 0;
+
+  // Asks the PolicyService to reload policy from all available policy sources.
+  // |callback| is invoked once every source has reloaded its policies, and
+  // GetPolicies() is guaranteed to return the updated values at that point.
+  virtual void RefreshPolicies(const base::Closure& callback) = 0;
+};
+
+// A registrar that only observes changes to particular policies within the
+// PolicyMap for the given policy namespace.
+class POLICY_EXPORT PolicyChangeRegistrar : public PolicyService::Observer {
+ public:
+  typedef base::Callback<void(const Value*, const Value*)> UpdateCallback;
+
+  // Observes updates to the given (domain, component_id) namespace in the given
+  // |policy_service|, and notifies |observer| whenever any of the registered
+  // policy keys changes. Both the |policy_service| and the |observer| must
+  // outlive |this|.
+  PolicyChangeRegistrar(PolicyService* policy_service,
+                        const PolicyNamespace& ns);
+
+  virtual ~PolicyChangeRegistrar();
+
+  // Will invoke |callback| whenever |policy_name| changes its value, as long
+  // as this registrar exists.
+  // Only one callback can be registed per policy name; a second call with the
+  // same |policy_name| will overwrite the previous callback.
+  void Observe(const std::string& policy_name, const UpdateCallback& callback);
+
+  // Implementation of PolicyService::Observer:
+  virtual void OnPolicyUpdated(const PolicyNamespace& ns,
+                               const PolicyMap& previous,
+                               const PolicyMap& current) OVERRIDE;
+
+ private:
+  typedef std::map<std::string, UpdateCallback> CallbackMap;
+
+  PolicyService* policy_service_;
+  PolicyNamespace ns_;
+  CallbackMap callback_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(PolicyChangeRegistrar);
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
diff --git a/components/policy/core/common/policy_service_impl.cc b/components/policy/core/common/policy_service_impl.cc
new file mode 100644
index 0000000..8fdd265
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2012 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 "components/policy/core/common/policy_service_impl.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+typedef PolicyServiceImpl::Providers::const_iterator Iterator;
+
+PolicyServiceImpl::PolicyServiceImpl(
+    const Providers& providers,
+    const PreprocessCallback& preprocess_callback)
+    : preprocess_callback_(preprocess_callback),
+      update_task_ptr_factory_(this) {
+  for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain)
+    initialization_complete_[domain] = true;
+  providers_ = providers;
+  for (Iterator it = providers.begin(); it != providers.end(); ++it) {
+    ConfigurationPolicyProvider* provider = *it;
+    provider->AddObserver(this);
+    for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
+      initialization_complete_[domain] &=
+          provider->IsInitializationComplete(static_cast<PolicyDomain>(domain));
+    }
+  }
+  // There are no observers yet, but calls to GetPolicies() should already get
+  // the processed policy values.
+  MergeAndTriggerUpdates();
+}
+
+PolicyServiceImpl::~PolicyServiceImpl() {
+  for (Iterator it = providers_.begin(); it != providers_.end(); ++it)
+    (*it)->RemoveObserver(this);
+  STLDeleteValues(&observers_);
+}
+
+void PolicyServiceImpl::AddObserver(PolicyDomain domain,
+                                    PolicyService::Observer* observer) {
+  Observers*& list = observers_[domain];
+  if (!list)
+    list = new Observers();
+  list->AddObserver(observer);
+}
+
+void PolicyServiceImpl::RemoveObserver(PolicyDomain domain,
+                                       PolicyService::Observer* observer) {
+  ObserverMap::iterator it = observers_.find(domain);
+  if (it == observers_.end()) {
+    NOTREACHED();
+    return;
+  }
+  it->second->RemoveObserver(observer);
+  if (!it->second->might_have_observers()) {
+    delete it->second;
+    observers_.erase(it);
+  }
+}
+
+const PolicyMap& PolicyServiceImpl::GetPolicies(
+    const PolicyNamespace& ns) const {
+  return policy_bundle_.Get(ns);
+}
+
+bool PolicyServiceImpl::IsInitializationComplete(PolicyDomain domain) const {
+  DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE);
+  return initialization_complete_[domain];
+}
+
+void PolicyServiceImpl::RefreshPolicies(const base::Closure& callback) {
+  if (!callback.is_null())
+    refresh_callbacks_.push_back(callback);
+
+  if (providers_.empty()) {
+    // Refresh is immediately complete if there are no providers. See the note
+    // on OnUpdatePolicy() about why this is a posted task.
+    update_task_ptr_factory_.InvalidateWeakPtrs();
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(&PolicyServiceImpl::MergeAndTriggerUpdates,
+                   update_task_ptr_factory_.GetWeakPtr()));
+  } else {
+    // Some providers might invoke OnUpdatePolicy synchronously while handling
+    // RefreshPolicies. Mark all as pending before refreshing.
+    for (Iterator it = providers_.begin(); it != providers_.end(); ++it)
+      refresh_pending_.insert(*it);
+    for (Iterator it = providers_.begin(); it != providers_.end(); ++it)
+      (*it)->RefreshPolicies();
+  }
+}
+
+void PolicyServiceImpl::OnUpdatePolicy(ConfigurationPolicyProvider* provider) {
+  DCHECK_EQ(1, std::count(providers_.begin(), providers_.end(), provider));
+  refresh_pending_.erase(provider);
+
+  // Note: a policy change may trigger further policy changes in some providers.
+  // For example, disabling SigninAllowed would cause the CloudPolicyManager to
+  // drop all its policies, which makes this method enter again for that
+  // provider.
+  //
+  // Therefore this update is posted asynchronously, to prevent reentrancy in
+  // MergeAndTriggerUpdates. Also, cancel a pending update if there is any,
+  // since both will produce the same PolicyBundle.
+  update_task_ptr_factory_.InvalidateWeakPtrs();
+  base::MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(&PolicyServiceImpl::MergeAndTriggerUpdates,
+                 update_task_ptr_factory_.GetWeakPtr()));
+}
+
+void PolicyServiceImpl::NotifyNamespaceUpdated(
+    const PolicyNamespace& ns,
+    const PolicyMap& previous,
+    const PolicyMap& current) {
+  ObserverMap::iterator iterator = observers_.find(ns.domain);
+  if (iterator != observers_.end()) {
+    FOR_EACH_OBSERVER(PolicyService::Observer,
+                      *iterator->second,
+                      OnPolicyUpdated(ns, previous, current));
+  }
+}
+
+void PolicyServiceImpl::MergeAndTriggerUpdates() {
+  // Merge from each provider in their order of priority.
+  PolicyBundle bundle;
+  for (Iterator it = providers_.begin(); it != providers_.end(); ++it) {
+    PolicyBundle provided_bundle;
+    provided_bundle.CopyFrom((*it)->policies());
+    if (!preprocess_callback_.is_null())
+      preprocess_callback_.Run(&provided_bundle);
+    bundle.MergeFrom(provided_bundle);
+  }
+
+  // Swap first, so that observers that call GetPolicies() see the current
+  // values.
+  policy_bundle_.Swap(&bundle);
+
+  // Only notify observers of namespaces that have been modified.
+  const PolicyMap kEmpty;
+  PolicyBundle::const_iterator it_new = policy_bundle_.begin();
+  PolicyBundle::const_iterator end_new = policy_bundle_.end();
+  PolicyBundle::const_iterator it_old = bundle.begin();
+  PolicyBundle::const_iterator end_old = bundle.end();
+  while (it_new != end_new && it_old != end_old) {
+    if (it_new->first < it_old->first) {
+      // A new namespace is available.
+      NotifyNamespaceUpdated(it_new->first, kEmpty, *it_new->second);
+      ++it_new;
+    } else if (it_old->first < it_new->first) {
+      // A previously available namespace is now gone.
+      NotifyNamespaceUpdated(it_old->first, *it_old->second, kEmpty);
+      ++it_old;
+    } else {
+      if (!it_new->second->Equals(*it_old->second)) {
+        // An existing namespace's policies have changed.
+        NotifyNamespaceUpdated(it_new->first, *it_old->second, *it_new->second);
+      }
+      ++it_new;
+      ++it_old;
+    }
+  }
+
+  // Send updates for the remaining new namespaces, if any.
+  for (; it_new != end_new; ++it_new)
+    NotifyNamespaceUpdated(it_new->first, kEmpty, *it_new->second);
+
+  // Sends updates for the remaining removed namespaces, if any.
+  for (; it_old != end_old; ++it_old)
+    NotifyNamespaceUpdated(it_old->first, *it_old->second, kEmpty);
+
+  CheckInitializationComplete();
+  CheckRefreshComplete();
+}
+
+void PolicyServiceImpl::CheckInitializationComplete() {
+  // Check if all the providers just became initialized for each domain; if so,
+  // notify that domain's observers.
+  for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
+    if (initialization_complete_[domain])
+      continue;
+
+    PolicyDomain policy_domain = static_cast<PolicyDomain>(domain);
+
+    bool all_complete = true;
+    for (Iterator it = providers_.begin(); it != providers_.end(); ++it) {
+      if (!(*it)->IsInitializationComplete(policy_domain)) {
+        all_complete = false;
+        break;
+      }
+    }
+    if (all_complete) {
+      initialization_complete_[domain] = true;
+      ObserverMap::iterator iter = observers_.find(policy_domain);
+      if (iter != observers_.end()) {
+        FOR_EACH_OBSERVER(PolicyService::Observer,
+                          *iter->second,
+                          OnPolicyServiceInitialized(policy_domain));
+      }
+    }
+  }
+}
+
+void PolicyServiceImpl::CheckRefreshComplete() {
+  // Invoke all the callbacks if a refresh has just fully completed.
+  if (refresh_pending_.empty() && !refresh_callbacks_.empty()) {
+    std::vector<base::Closure> callbacks;
+    callbacks.swap(refresh_callbacks_);
+    std::vector<base::Closure>::iterator it;
+    for (it = callbacks.begin(); it != callbacks.end(); ++it)
+      it->Run();
+  }
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_service_impl.h b/components/policy/core/common/policy_service_impl.h
new file mode 100644
index 0000000..a10e881
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyMap;
+
+class POLICY_EXPORT PolicyServiceImpl
+    : public PolicyService,
+      public ConfigurationPolicyProvider::Observer {
+ public:
+  typedef std::vector<ConfigurationPolicyProvider*> Providers;
+  typedef base::Callback<void(PolicyBundle*)> PreprocessCallback;
+
+  // The PolicyServiceImpl will merge policies from |providers|. |providers|
+  // must be sorted in decreasing order of priority; the first provider will
+  // have the highest priority. The PolicyServiceImpl does not take ownership of
+  // the providers, and they must outlive the PolicyServiceImpl.
+  // |preprocess_callback| will be applied every PolicyBundle before merginng.
+  PolicyServiceImpl(const Providers& providers,
+                    const PreprocessCallback& preprocess_callback);
+
+  virtual ~PolicyServiceImpl();
+
+  // PolicyService overrides:
+  virtual void AddObserver(PolicyDomain domain,
+                           PolicyService::Observer* observer) OVERRIDE;
+  virtual void RemoveObserver(PolicyDomain domain,
+                              PolicyService::Observer* observer) OVERRIDE;
+  virtual const PolicyMap& GetPolicies(
+      const PolicyNamespace& ns) const OVERRIDE;
+  virtual bool IsInitializationComplete(PolicyDomain domain) const OVERRIDE;
+  virtual void RefreshPolicies(const base::Closure& callback) OVERRIDE;
+
+ private:
+  typedef ObserverList<PolicyService::Observer, true> Observers;
+  typedef std::map<PolicyDomain, Observers*> ObserverMap;
+
+  // ConfigurationPolicyProvider::Observer overrides:
+  virtual void OnUpdatePolicy(ConfigurationPolicyProvider* provider) OVERRIDE;
+
+  // Posts a task to notify observers of |ns| that its policies have changed,
+  // passing along the |previous| and the |current| policies.
+  void NotifyNamespaceUpdated(const PolicyNamespace& ns,
+                              const PolicyMap& previous,
+                              const PolicyMap& current);
+
+  // Combines the policies from all the providers, and notifies the observers
+  // of namespaces whose policies have been modified.
+  void MergeAndTriggerUpdates();
+
+  // Checks if all providers are initialized, and notifies the observers
+  // if the service just became initialized.
+  void CheckInitializationComplete();
+
+  // Invokes all the refresh callbacks if there are no more refreshes pending.
+  void CheckRefreshComplete();
+
+  // The providers passed in the constructor, in order of decreasing priority.
+  Providers providers_;
+
+  // Maps each policy namespace to its current policies.
+  PolicyBundle policy_bundle_;
+
+  // Maps each policy domain to its observer list.
+  ObserverMap observers_;
+
+  // True if all the providers are initialized for the indexed policy domain.
+  bool initialization_complete_[POLICY_DOMAIN_SIZE];
+
+  // Set of providers that have a pending update that was triggered by a
+  // call to RefreshPolicies().
+  std::set<ConfigurationPolicyProvider*> refresh_pending_;
+
+  // Callback invoked to manipulate a PolicyBundle before it is merged.
+  PreprocessCallback preprocess_callback_;
+
+  // List of callbacks to invoke once all providers refresh after a
+  // RefreshPolicies() call.
+  std::vector<base::Closure> refresh_callbacks_;
+
+  // Used to create tasks to delay new policy updates while we may be already
+  // processing previous policy updates.
+  base::WeakPtrFactory<PolicyServiceImpl> update_task_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(PolicyServiceImpl);
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
diff --git a/components/policy/core/common/policy_service_impl_unittest.cc b/components/policy/core/common/policy_service_impl_unittest.cc
new file mode 100644
index 0000000..e95a1fb
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl_unittest.cc
@@ -0,0 +1,651 @@
+// Copyright (c) 2012 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 "components/policy/core/common/policy_service_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AnyNumber;
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::_;
+
+namespace policy {
+
+namespace {
+
+const char kExtension[] = "extension-id";
+const char kSameLevelPolicy[] = "policy-same-level-and-scope";
+const char kDiffLevelPolicy[] = "chrome-diff-level-and-scope";
+
+void SetPolicyMapValue(const std::string& key,
+                       const std::string& value,
+                       PolicyBundle* bundle) {
+  bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+      .Set(key,
+           POLICY_LEVEL_MANDATORY,
+           POLICY_SCOPE_USER,
+           new base::StringValue(value),
+           NULL);
+}
+
+// Helper to compare the arguments to an EXPECT_CALL of OnPolicyUpdated() with
+// their expected values.
+MATCHER_P(PolicyEquals, expected, "") {
+  return arg.Equals(*expected);
+}
+
+// Helper to compare the arguments to an EXPECT_CALL of OnPolicyValueUpdated()
+// with their expected values.
+MATCHER_P(ValueEquals, expected, "") {
+  return base::Value::Equals(arg, expected);
+}
+
+// Helper that fills |bundle| with test policies.
+void AddTestPolicies(PolicyBundle* bundle,
+                     const char* value,
+                     PolicyLevel level,
+                     PolicyScope scope) {
+  PolicyMap* policy_map =
+      &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+  policy_map->Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                  base::Value::CreateStringValue(value), NULL);
+  policy_map->Set(kDiffLevelPolicy, level, scope,
+                  base::Value::CreateStringValue(value), NULL);
+  policy_map =
+      &bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension));
+  policy_map->Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY,
+                  POLICY_SCOPE_USER, base::Value::CreateStringValue(value),
+                  NULL);
+  policy_map->Set(kDiffLevelPolicy, level, scope,
+                  base::Value::CreateStringValue(value), NULL);
+}
+
+// Observer class that changes the policy in the passed provider when the
+// callback is invoked.
+class ChangePolicyObserver : public PolicyService::Observer {
+ public:
+  explicit ChangePolicyObserver(MockConfigurationPolicyProvider* provider)
+      : provider_(provider),
+        observer_invoked_(false) {}
+
+  virtual void OnPolicyUpdated(const PolicyNamespace&,
+                               const PolicyMap& previous,
+                               const PolicyMap& current) OVERRIDE {
+    PolicyMap new_policy;
+    new_policy.Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                   base::Value::CreateIntegerValue(14), NULL);
+    provider_->UpdateChromePolicy(new_policy);
+    observer_invoked_ = true;
+  }
+
+  bool observer_invoked() const { return observer_invoked_; }
+
+ private:
+  MockConfigurationPolicyProvider* provider_;
+  bool observer_invoked_;
+};
+
+}  // namespace
+
+class PolicyServiceTest : public testing::Test {
+ public:
+  PolicyServiceTest() {}
+  virtual void SetUp() OVERRIDE {
+    EXPECT_CALL(provider0_, IsInitializationComplete(_))
+        .WillRepeatedly(Return(true));
+    EXPECT_CALL(provider1_, IsInitializationComplete(_))
+        .WillRepeatedly(Return(true));
+    EXPECT_CALL(provider2_, IsInitializationComplete(_))
+        .WillRepeatedly(Return(true));
+
+    provider0_.Init();
+    provider1_.Init();
+    provider2_.Init();
+
+    policy0_.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                 base::Value::CreateIntegerValue(13), NULL);
+    provider0_.UpdateChromePolicy(policy0_);
+
+    PolicyServiceImpl::Providers providers;
+    providers.push_back(&provider0_);
+    providers.push_back(&provider1_);
+    providers.push_back(&provider2_);
+    policy_service_.reset(new PolicyServiceImpl(
+        providers, PolicyServiceImpl::PreprocessCallback()));
+  }
+
+  virtual void TearDown() OVERRIDE {
+    provider0_.Shutdown();
+    provider1_.Shutdown();
+    provider2_.Shutdown();
+  }
+
+  MOCK_METHOD2(OnPolicyValueUpdated, void(const base::Value*,
+                                          const base::Value*));
+
+  MOCK_METHOD0(OnPolicyRefresh, void());
+
+  // Returns true if the policies for namespace |ns| match |expected|.
+  bool VerifyPolicies(const PolicyNamespace& ns,
+                      const PolicyMap& expected) {
+    return policy_service_->GetPolicies(ns).Equals(expected);
+  }
+
+  void RunUntilIdle() {
+    base::RunLoop loop;
+    loop.RunUntilIdle();
+  }
+
+ protected:
+  base::MessageLoop loop_;
+  MockConfigurationPolicyProvider provider0_;
+  MockConfigurationPolicyProvider provider1_;
+  MockConfigurationPolicyProvider provider2_;
+  PolicyMap policy0_;
+  PolicyMap policy1_;
+  PolicyMap policy2_;
+  scoped_ptr<PolicyServiceImpl> policy_service_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PolicyServiceTest);
+};
+
+TEST_F(PolicyServiceTest, LoadsPoliciesBeforeProvidersRefresh) {
+  PolicyMap expected;
+  expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(13), NULL);
+  EXPECT_TRUE(VerifyPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+}
+
+TEST_F(PolicyServiceTest, NotifyObservers) {
+  MockPolicyServiceObserver observer;
+  policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+
+  PolicyMap expectedPrevious;
+  expectedPrevious.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                       base::Value::CreateIntegerValue(13), NULL);
+
+  PolicyMap expectedCurrent;
+  expectedCurrent.CopyFrom(expectedPrevious);
+  expectedCurrent.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                      base::Value::CreateIntegerValue(123), NULL);
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(123), NULL);
+  EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+                                                        std::string()),
+                                        PolicyEquals(&expectedPrevious),
+                                        PolicyEquals(&expectedCurrent)));
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(&observer);
+
+  // No changes.
+  EXPECT_CALL(observer, OnPolicyUpdated(_, _, _)).Times(0);
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(&observer);
+  EXPECT_TRUE(VerifyPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expectedCurrent));
+
+  // New policy.
+  expectedPrevious.CopyFrom(expectedCurrent);
+  expectedCurrent.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                      base::Value::CreateIntegerValue(456), NULL);
+  policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(456), NULL);
+  EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+                                                        std::string()),
+                                        PolicyEquals(&expectedPrevious),
+                                        PolicyEquals(&expectedCurrent)));
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(&observer);
+
+  // Removed policy.
+  expectedPrevious.CopyFrom(expectedCurrent);
+  expectedCurrent.Erase("bbb");
+  policy0_.Erase("bbb");
+  EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+                                                        std::string()),
+                                        PolicyEquals(&expectedPrevious),
+                                        PolicyEquals(&expectedCurrent)));
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(&observer);
+
+  // Changed policy.
+  expectedPrevious.CopyFrom(expectedCurrent);
+  expectedCurrent.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                      base::Value::CreateIntegerValue(789), NULL);
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(789), NULL);
+
+  EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+                                                        std::string()),
+                                        PolicyEquals(&expectedPrevious),
+                                        PolicyEquals(&expectedCurrent)));
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(&observer);
+
+  // No changes again.
+  EXPECT_CALL(observer, OnPolicyUpdated(_, _, _)).Times(0);
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(&observer);
+  EXPECT_TRUE(VerifyPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expectedCurrent));
+
+  policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+}
+
+TEST_F(PolicyServiceTest, NotifyObserversInMultipleNamespaces) {
+  const std::string kExtension0("extension-0");
+  const std::string kExtension1("extension-1");
+  const std::string kExtension2("extension-2");
+  MockPolicyServiceObserver chrome_observer;
+  MockPolicyServiceObserver extension_observer;
+  policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &chrome_observer);
+  policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &extension_observer);
+
+  PolicyMap previous_policy_map;
+  previous_policy_map.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                          base::Value::CreateIntegerValue(13), NULL);
+  PolicyMap policy_map;
+  policy_map.CopyFrom(previous_policy_map);
+  policy_map.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                 base::Value::CreateStringValue("value"), NULL);
+
+  scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
+  // The initial setup includes a policy for chrome that is now changing.
+  bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+      .CopyFrom(policy_map);
+  bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0))
+      .CopyFrom(policy_map);
+  bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1))
+      .CopyFrom(policy_map);
+
+  const PolicyMap kEmptyPolicyMap;
+  EXPECT_CALL(
+      chrome_observer,
+      OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()),
+                      PolicyEquals(&previous_policy_map),
+                      PolicyEquals(&policy_map)));
+  EXPECT_CALL(
+      extension_observer,
+      OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0),
+                      PolicyEquals(&kEmptyPolicyMap),
+                      PolicyEquals(&policy_map)));
+  EXPECT_CALL(
+      extension_observer,
+      OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1),
+                      PolicyEquals(&kEmptyPolicyMap),
+                      PolicyEquals(&policy_map)));
+  provider0_.UpdatePolicy(bundle.Pass());
+  RunUntilIdle();
+  Mock::VerifyAndClearExpectations(&chrome_observer);
+  Mock::VerifyAndClearExpectations(&extension_observer);
+
+  // Chrome policy stays the same, kExtension0 is gone, kExtension1 changes,
+  // and kExtension2 is new.
+  previous_policy_map.CopyFrom(policy_map);
+  bundle.reset(new PolicyBundle());
+  bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+      .CopyFrom(policy_map);
+  policy_map.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                 base::Value::CreateStringValue("another value"), NULL);
+  bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1))
+      .CopyFrom(policy_map);
+  bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2))
+      .CopyFrom(policy_map);
+
+  EXPECT_CALL(chrome_observer, OnPolicyUpdated(_, _, _)).Times(0);
+  EXPECT_CALL(
+      extension_observer,
+      OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0),
+                      PolicyEquals(&previous_policy_map),
+                      PolicyEquals(&kEmptyPolicyMap)));
+  EXPECT_CALL(
+      extension_observer,
+      OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1),
+                      PolicyEquals(&previous_policy_map),
+                      PolicyEquals(&policy_map)));
+  EXPECT_CALL(
+      extension_observer,
+      OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2),
+                      PolicyEquals(&kEmptyPolicyMap),
+                      PolicyEquals(&policy_map)));
+  provider0_.UpdatePolicy(bundle.Pass());
+  RunUntilIdle();
+  Mock::VerifyAndClearExpectations(&chrome_observer);
+  Mock::VerifyAndClearExpectations(&extension_observer);
+
+  policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &chrome_observer);
+  policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS,
+                                  &extension_observer);
+}
+
+TEST_F(PolicyServiceTest, ObserverChangesPolicy) {
+  ChangePolicyObserver observer(&provider0_);
+  policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(123), NULL);
+  policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(1234), NULL);
+  // Should not crash.
+  provider0_.UpdateChromePolicy(policy0_);
+  policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+  EXPECT_TRUE(observer.observer_invoked());
+}
+
+TEST_F(PolicyServiceTest, Priorities) {
+  PolicyMap expected;
+  expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(13), NULL);
+  expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(0), NULL);
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(0), NULL);
+  policy1_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(1), NULL);
+  policy2_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(2), NULL);
+  provider0_.UpdateChromePolicy(policy0_);
+  provider1_.UpdateChromePolicy(policy1_);
+  provider2_.UpdateChromePolicy(policy2_);
+  EXPECT_TRUE(VerifyPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+
+  expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(1), NULL);
+  policy0_.Erase("aaa");
+  provider0_.UpdateChromePolicy(policy0_);
+  EXPECT_TRUE(VerifyPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+
+  expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(2), NULL);
+  policy1_.Set("aaa", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+               base::Value::CreateIntegerValue(1), NULL);
+  provider1_.UpdateChromePolicy(policy1_);
+  EXPECT_TRUE(VerifyPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+}
+
+TEST_F(PolicyServiceTest, PolicyChangeRegistrar) {
+  scoped_ptr<PolicyChangeRegistrar> registrar(new PolicyChangeRegistrar(
+      policy_service_.get(),
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+
+  // Starting to observe existing policies doesn't trigger a notification.
+  EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+  registrar->Observe("pre", base::Bind(
+      &PolicyServiceTest::OnPolicyValueUpdated,
+      base::Unretained(this)));
+  registrar->Observe("aaa", base::Bind(
+      &PolicyServiceTest::OnPolicyValueUpdated,
+      base::Unretained(this)));
+  RunUntilIdle();
+  Mock::VerifyAndClearExpectations(this);
+
+  // Changing it now triggers a notification.
+  base::FundamentalValue kValue0(0);
+  EXPECT_CALL(*this, OnPolicyValueUpdated(NULL, ValueEquals(&kValue0)));
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               kValue0.DeepCopy(), NULL);
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // Changing other values doesn't trigger a notification.
+  EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+  policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               kValue0.DeepCopy(), NULL);
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // Modifying the value triggers a notification.
+  base::FundamentalValue kValue1(1);
+  EXPECT_CALL(*this, OnPolicyValueUpdated(ValueEquals(&kValue0),
+                                          ValueEquals(&kValue1)));
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               kValue1.DeepCopy(), NULL);
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // Removing the value triggers a notification.
+  EXPECT_CALL(*this, OnPolicyValueUpdated(ValueEquals(&kValue1), NULL));
+  policy0_.Erase("aaa");
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // No more notifications after destroying the registrar.
+  EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+  registrar.reset();
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               kValue1.DeepCopy(), NULL);
+  policy0_.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               kValue1.DeepCopy(), NULL);
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(this);
+}
+
+TEST_F(PolicyServiceTest, RefreshPolicies) {
+  EXPECT_CALL(provider0_, RefreshPolicies()).Times(AnyNumber());
+  EXPECT_CALL(provider1_, RefreshPolicies()).Times(AnyNumber());
+  EXPECT_CALL(provider2_, RefreshPolicies()).Times(AnyNumber());
+
+  EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+  policy_service_->RefreshPolicies(base::Bind(
+      &PolicyServiceTest::OnPolicyRefresh,
+      base::Unretained(this)));
+  // Let any queued observer tasks run.
+  RunUntilIdle();
+  Mock::VerifyAndClearExpectations(this);
+
+  EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+  base::FundamentalValue kValue0(0);
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               kValue0.DeepCopy(), NULL);
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(this);
+
+  EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+  base::FundamentalValue kValue1(1);
+  policy1_.Set("aaa", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+               kValue1.DeepCopy(), NULL);
+  provider1_.UpdateChromePolicy(policy1_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // A provider can refresh more than once after a RefreshPolicies call, but
+  // OnPolicyRefresh should be triggered only after all providers are
+  // refreshed.
+  EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+  policy1_.Set("bbb", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+               kValue1.DeepCopy(), NULL);
+  provider1_.UpdateChromePolicy(policy1_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // If another RefreshPolicies() call happens while waiting for a previous
+  // one to complete, then all providers must refresh again.
+  EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+  policy_service_->RefreshPolicies(base::Bind(
+      &PolicyServiceTest::OnPolicyRefresh,
+      base::Unretained(this)));
+  RunUntilIdle();
+  Mock::VerifyAndClearExpectations(this);
+
+  EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+  policy2_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               kValue0.DeepCopy(), NULL);
+  provider2_.UpdateChromePolicy(policy2_);
+  Mock::VerifyAndClearExpectations(this);
+
+  // Providers 0 and 1 must reload again.
+  EXPECT_CALL(*this, OnPolicyRefresh()).Times(2);
+  base::FundamentalValue kValue2(2);
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               kValue2.DeepCopy(), NULL);
+  provider0_.UpdateChromePolicy(policy0_);
+  provider1_.UpdateChromePolicy(policy1_);
+  Mock::VerifyAndClearExpectations(this);
+
+  const PolicyMap& policies = policy_service_->GetPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+  EXPECT_TRUE(base::Value::Equals(&kValue2, policies.GetValue("aaa")));
+  EXPECT_TRUE(base::Value::Equals(&kValue0, policies.GetValue("bbb")));
+}
+
+TEST_F(PolicyServiceTest, NamespaceMerge) {
+  scoped_ptr<PolicyBundle> bundle0(new PolicyBundle());
+  scoped_ptr<PolicyBundle> bundle1(new PolicyBundle());
+  scoped_ptr<PolicyBundle> bundle2(new PolicyBundle());
+
+  AddTestPolicies(bundle0.get(), "bundle0",
+                  POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER);
+  AddTestPolicies(bundle1.get(), "bundle1",
+                  POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
+  AddTestPolicies(bundle2.get(), "bundle2",
+                  POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
+
+  provider0_.UpdatePolicy(bundle0.Pass());
+  provider1_.UpdatePolicy(bundle1.Pass());
+  provider2_.UpdatePolicy(bundle2.Pass());
+  RunUntilIdle();
+
+  PolicyMap expected;
+  // For policies of the same level and scope, the first provider takes
+  // precedence, on every namespace.
+  expected.Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               base::Value::CreateStringValue("bundle0"), NULL);
+  // For policies with different levels and scopes, the highest priority
+  // level/scope combination takes precedence, on every namespace.
+  expected.Set(kDiffLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+               base::Value::CreateStringValue("bundle2"), NULL);
+  EXPECT_TRUE(policy_service_->GetPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())).Equals(expected));
+  EXPECT_TRUE(policy_service_->GetPolicies(
+      PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension)).Equals(expected));
+}
+
+TEST_F(PolicyServiceTest, PolicyPreprocessing) {
+  // Reset the PolicyServiceImpl to one that has the preprocessor.
+  PolicyServiceImpl::Providers providers;
+  providers.push_back(&provider0_);
+  policy_service_.reset(new PolicyServiceImpl(
+      providers, base::Bind(&SetPolicyMapValue, kSameLevelPolicy, "bar")));
+
+  // Set the policy value to "foo".
+  scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
+  PolicyMap& map =
+      bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+  map.Set(kSameLevelPolicy,
+          POLICY_LEVEL_MANDATORY,
+          POLICY_SCOPE_USER,
+          base::Value::CreateStringValue("foo"),
+          NULL);
+
+  // Push the update through the provider.
+  provider0_.UpdatePolicy(bundle.Pass());
+  RunUntilIdle();
+
+  // The value should have been changed from "foo" to "bar".
+  const PolicyMap& actual = policy_service_->GetPolicies(
+        PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+  PolicyMap expected;
+  expected.Set(kSameLevelPolicy,
+               POLICY_LEVEL_MANDATORY,
+               POLICY_SCOPE_USER,
+               base::Value::CreateStringValue("bar"),
+               NULL);
+  EXPECT_TRUE(actual.Equals(expected));
+}
+
+TEST_F(PolicyServiceTest, IsInitializationComplete) {
+  // |provider0| has all domains initialized.
+  Mock::VerifyAndClearExpectations(&provider1_);
+  Mock::VerifyAndClearExpectations(&provider2_);
+  EXPECT_CALL(provider1_, IsInitializationComplete(_))
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(provider2_, IsInitializationComplete(_))
+      .WillRepeatedly(Return(false));
+  PolicyServiceImpl::Providers providers;
+  providers.push_back(&provider0_);
+  providers.push_back(&provider1_);
+  providers.push_back(&provider2_);
+  policy_service_.reset(new PolicyServiceImpl(
+      providers, PolicyServiceImpl::PreprocessCallback()));
+  EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+  EXPECT_FALSE(
+      policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+  // |provider2_| still doesn't have POLICY_DOMAIN_CHROME initialized, so
+  // the initialization status of that domain won't change.
+  MockPolicyServiceObserver observer;
+  policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+  policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+  EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+  Mock::VerifyAndClearExpectations(&provider1_);
+  EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+      .WillRepeatedly(Return(false));
+  const PolicyMap kPolicyMap;
+  provider1_.UpdateChromePolicy(kPolicyMap);
+  Mock::VerifyAndClearExpectations(&observer);
+  EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+  EXPECT_FALSE(
+      policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+  // Same if |provider1_| doesn't have POLICY_DOMAIN_EXTENSIONS initialized.
+  EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+  Mock::VerifyAndClearExpectations(&provider2_);
+  EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+      .WillRepeatedly(Return(true));
+  provider2_.UpdateChromePolicy(kPolicyMap);
+  Mock::VerifyAndClearExpectations(&observer);
+  EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+  EXPECT_FALSE(
+      policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+  // Now initialize POLICY_DOMAIN_CHROME on all the providers.
+  EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME));
+  Mock::VerifyAndClearExpectations(&provider2_);
+  EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+      .WillRepeatedly(Return(true));
+  provider2_.UpdateChromePolicy(kPolicyMap);
+  Mock::VerifyAndClearExpectations(&observer);
+  EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+  // Other domains are still not initialized.
+  EXPECT_FALSE(
+      policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+  // Initialize the remaining domain.
+  EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_EXTENSIONS));
+  Mock::VerifyAndClearExpectations(&provider1_);
+  EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+      .WillRepeatedly(Return(true));
+  provider1_.UpdateChromePolicy(kPolicyMap);
+  Mock::VerifyAndClearExpectations(&observer);
+  EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+  EXPECT_TRUE(
+      policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+  // Cleanup.
+  policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+  policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_service_stub.cc b/components/policy/core/common/policy_service_stub.cc
new file mode 100644
index 0000000..1f9682b
--- /dev/null
+++ b/components/policy/core/common/policy_service_stub.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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 "components/policy/core/common/policy_service_stub.h"
+
+#include "base/message_loop/message_loop.h"
+
+namespace policy {
+
+PolicyServiceStub::PolicyServiceStub() {}
+
+PolicyServiceStub::~PolicyServiceStub() {}
+
+void PolicyServiceStub::AddObserver(PolicyDomain domain,
+                                    Observer* observer) {}
+
+void PolicyServiceStub::RemoveObserver(PolicyDomain domain,
+                                       Observer* observer) {}
+
+const PolicyMap& PolicyServiceStub::GetPolicies(
+    const PolicyNamespace& ns) const {
+  return kEmpty_;
+};
+
+bool PolicyServiceStub::IsInitializationComplete(PolicyDomain domain) const {
+  return true;
+}
+
+void PolicyServiceStub::RefreshPolicies(const base::Closure& callback) {
+  if (!callback.is_null())
+    callback.Run();
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_service_stub.h b/components/policy/core/common/policy_service_stub.h
new file mode 100644
index 0000000..f7dee23
--- /dev/null
+++ b/components/policy/core/common/policy_service_stub.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
+
+#include "base/basictypes.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A stub implementation, that is used when ENABLE_CONFIGURATION_POLICY is not
+// set. This allows client code to compile without requiring #ifdefs.
+class POLICY_EXPORT PolicyServiceStub : public PolicyService {
+ public:
+  PolicyServiceStub();
+  virtual ~PolicyServiceStub();
+
+  virtual void AddObserver(PolicyDomain domain,
+                           Observer* observer) OVERRIDE;
+
+  virtual void RemoveObserver(PolicyDomain domain,
+                              Observer* observer) OVERRIDE;
+
+  virtual const PolicyMap& GetPolicies(
+      const PolicyNamespace& ns) const OVERRIDE;
+
+  virtual bool IsInitializationComplete(PolicyDomain domain) const OVERRIDE;
+
+  virtual void RefreshPolicies(const base::Closure& callback) OVERRIDE;
+ private:
+  const PolicyMap kEmpty_;
+
+  DISALLOW_COPY_AND_ASSIGN(PolicyServiceStub);
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
diff --git a/components/policy/core/common/policy_statistics_collector.cc b/components/policy/core/common/policy_statistics_collector.cc
new file mode 100644
index 0000000..3e6a588
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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 "components/policy/core/common/policy_statistics_collector.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "base/prefs/pref_service.h"
+#include "base/task_runner.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_service.h"
+
+namespace policy {
+
+const int PolicyStatisticsCollector::kStatisticsUpdateRate =
+    24 * 60 * 60 * 1000;  // 24 hours.
+
+PolicyStatisticsCollector::PolicyStatisticsCollector(
+    const GetChromePolicyDetailsCallback& get_details,
+    const Schema& chrome_schema,
+    PolicyService* policy_service,
+    PrefService* prefs,
+    const scoped_refptr<base::TaskRunner>& task_runner)
+    : get_details_(get_details),
+      chrome_schema_(chrome_schema),
+      policy_service_(policy_service),
+      prefs_(prefs),
+      task_runner_(task_runner) {
+}
+
+PolicyStatisticsCollector::~PolicyStatisticsCollector() {
+}
+
+void PolicyStatisticsCollector::Initialize() {
+  using base::Time;
+  using base::TimeDelta;
+
+  TimeDelta update_rate = TimeDelta::FromMilliseconds(kStatisticsUpdateRate);
+  Time last_update = Time::FromInternalValue(
+      prefs_->GetInt64(policy_prefs::kLastPolicyStatisticsUpdate));
+  TimeDelta delay = std::max(Time::Now() - last_update, TimeDelta::FromDays(0));
+  if (delay >= update_rate)
+    CollectStatistics();
+  else
+    ScheduleUpdate(update_rate - delay);
+}
+
+// static
+void PolicyStatisticsCollector::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterInt64Pref(policy_prefs::kLastPolicyStatisticsUpdate, 0);
+}
+
+void PolicyStatisticsCollector::RecordPolicyUse(int id) {
+  UMA_HISTOGRAM_SPARSE_SLOWLY("Enterprise.Policies", id);
+}
+
+void PolicyStatisticsCollector::CollectStatistics() {
+  const PolicyMap& policies = policy_service_->GetPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+
+  // Collect statistics.
+  for (Schema::Iterator it(chrome_schema_.GetPropertiesIterator());
+       !it.IsAtEnd(); it.Advance()) {
+    if (policies.Get(it.key())) {
+      const PolicyDetails* details = get_details_.Run(it.key());
+      if (details)
+        RecordPolicyUse(details->id);
+      else
+        NOTREACHED();
+    }
+  }
+
+  // Take care of next update.
+  prefs_->SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+                   base::Time::Now().ToInternalValue());
+  ScheduleUpdate(base::TimeDelta::FromMilliseconds(kStatisticsUpdateRate));
+}
+
+void PolicyStatisticsCollector::ScheduleUpdate(base::TimeDelta delay) {
+  update_callback_.Reset(base::Bind(
+      &PolicyStatisticsCollector::CollectStatistics,
+      base::Unretained(this)));
+  task_runner_->PostDelayedTask(FROM_HERE, update_callback_.callback(), delay);
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_statistics_collector.h b/components/policy/core/common/policy_statistics_collector.h
new file mode 100644
index 0000000..f062ea3
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
+
+#include "base/basictypes.h"
+#include "base/cancelable_callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace base {
+class TaskRunner;
+}
+
+namespace policy {
+
+class PolicyService;
+
+// Manages regular updates of policy usage UMA histograms.
+class POLICY_EXPORT PolicyStatisticsCollector {
+ public:
+  // Policy usage statistics update rate, in milliseconds.
+  static const int kStatisticsUpdateRate;
+
+  // Neither |policy_service| nor |prefs| can be NULL and must stay valid
+  // throughout the lifetime of PolicyStatisticsCollector.
+  PolicyStatisticsCollector(const GetChromePolicyDetailsCallback& get_details,
+                            const Schema& chrome_schema,
+                            PolicyService* policy_service,
+                            PrefService* prefs,
+                            const scoped_refptr<base::TaskRunner>& task_runner);
+  virtual ~PolicyStatisticsCollector();
+
+  // Completes initialization and starts periodical statistic updates.
+  void Initialize();
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ protected:
+  // protected virtual for mocking.
+  virtual void RecordPolicyUse(int id);
+
+ private:
+  void CollectStatistics();
+  void ScheduleUpdate(base::TimeDelta delay);
+
+  GetChromePolicyDetailsCallback get_details_;
+  Schema chrome_schema_;
+  PolicyService* policy_service_;
+  PrefService* prefs_;
+
+  base::CancelableClosure update_callback_;
+
+  const scoped_refptr<base::TaskRunner> task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(PolicyStatisticsCollector);
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
diff --git a/components/policy/core/common/policy_statistics_collector_unittest.cc b/components/policy/core/common/policy_statistics_collector_unittest.cc
new file mode 100644
index 0000000..3c1a0ca
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector_unittest.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 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 <cstring>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "base/prefs/testing_pref_service.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_statistics_collector.h"
+#include "components/policy/core/common/policy_test_utils.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+using testing::ReturnRef;
+
+// Arbitrary policy names used for testing.
+const char kTestPolicy1[] = "Test Policy 1";
+const char kTestPolicy2[] = "Test Policy 2";
+
+const int kTestPolicy1Id = 42;
+const int kTestPolicy2Id = 123;
+
+const char kTestChromeSchema[] =
+    "{"
+    "  \"type\": \"object\","
+    "  \"properties\": {"
+    "    \"Test Policy 1\": { \"type\": \"string\" },"
+    "    \"Test Policy 2\": { \"type\": \"string\" }"
+    "  }"
+    "}";
+
+const PolicyDetails kTestPolicyDetails[] = {
+  // is_deprecated  is_device_policy              id  max_external_data_size
+  {          false,            false, kTestPolicy1Id,                      0 },
+  {          false,            false, kTestPolicy2Id,                      0 },
+};
+
+class TestPolicyStatisticsCollector : public PolicyStatisticsCollector {
+ public:
+  TestPolicyStatisticsCollector(
+      const GetChromePolicyDetailsCallback& get_details,
+      const Schema& chrome_schema,
+      PolicyService* policy_service,
+      PrefService* prefs,
+      const scoped_refptr<base::TaskRunner>& task_runner)
+      : PolicyStatisticsCollector(get_details,
+                                  chrome_schema,
+                                  policy_service,
+                                  prefs,
+                                  task_runner) {}
+
+  MOCK_METHOD1(RecordPolicyUse, void(int));
+};
+
+}  // namespace
+
+class PolicyStatisticsCollectorTest : public testing::Test {
+ protected:
+  PolicyStatisticsCollectorTest()
+      : update_delay_(base::TimeDelta::FromMilliseconds(
+            PolicyStatisticsCollector::kStatisticsUpdateRate)),
+        task_runner_(new base::TestSimpleTaskRunner()) {
+  }
+
+  virtual void SetUp() OVERRIDE {
+    std::string error;
+    chrome_schema_ = Schema::Parse(kTestChromeSchema, &error);
+    ASSERT_TRUE(chrome_schema_.valid()) << error;
+
+    policy_details_.SetDetails(kTestPolicy1, &kTestPolicyDetails[0]);
+    policy_details_.SetDetails(kTestPolicy2, &kTestPolicyDetails[1]);
+
+    prefs_.registry()->RegisterInt64Pref(
+        policy_prefs::kLastPolicyStatisticsUpdate, 0);
+
+    // Set up default function behaviour.
+    EXPECT_CALL(policy_service_,
+                GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME,
+                                            std::string())))
+        .WillRepeatedly(ReturnRef(policy_map_));
+
+    // Arbitrary negative value (so it'll be different from |update_delay_|).
+    last_delay_ = base::TimeDelta::FromDays(-1);
+    policy_map_.Clear();
+    policy_statistics_collector_.reset(new TestPolicyStatisticsCollector(
+        policy_details_.GetCallback(),
+        chrome_schema_,
+        &policy_service_,
+        &prefs_,
+        task_runner_));
+  }
+
+  void SetPolicy(const std::string& name) {
+    policy_map_.Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                    base::Value::CreateBooleanValue(true), NULL);
+  }
+
+  base::TimeDelta GetFirstDelay() const {
+    if (task_runner_->GetPendingTasks().empty()) {
+      ADD_FAILURE();
+      return base::TimeDelta();
+    }
+    return task_runner_->GetPendingTasks().front().delay;
+  }
+
+  const base::TimeDelta update_delay_;
+
+  base::TimeDelta last_delay_;
+
+  PolicyDetailsMap policy_details_;
+  Schema chrome_schema_;
+  TestingPrefServiceSimple prefs_;
+  MockPolicyService policy_service_;
+  PolicyMap policy_map_;
+
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  scoped_ptr<TestPolicyStatisticsCollector> policy_statistics_collector_;
+};
+
+TEST_F(PolicyStatisticsCollectorTest, CollectPending) {
+  SetPolicy(kTestPolicy1);
+
+  prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+                  (base::Time::Now() - update_delay_).ToInternalValue());
+
+  EXPECT_CALL(*policy_statistics_collector_.get(),
+              RecordPolicyUse(kTestPolicy1Id));
+
+  policy_statistics_collector_->Initialize();
+  EXPECT_EQ(1u, task_runner_->GetPendingTasks().size());
+  EXPECT_EQ(update_delay_, GetFirstDelay());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, CollectPendingVeryOld) {
+  SetPolicy(kTestPolicy1);
+
+  // Must not be 0.0 (read comment for Time::FromDoubleT).
+  prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+                  base::Time::FromDoubleT(1.0).ToInternalValue());
+
+  EXPECT_CALL(*policy_statistics_collector_.get(),
+              RecordPolicyUse(kTestPolicy1Id));
+
+  policy_statistics_collector_->Initialize();
+  EXPECT_EQ(1u, task_runner_->GetPendingTasks().size());
+  EXPECT_EQ(update_delay_, GetFirstDelay());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, CollectLater) {
+  SetPolicy(kTestPolicy1);
+
+  prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+                  (base::Time::Now() - update_delay_ / 2).ToInternalValue());
+
+  policy_statistics_collector_->Initialize();
+  EXPECT_EQ(1u, task_runner_->GetPendingTasks().size());
+  EXPECT_LT(GetFirstDelay(), update_delay_);
+}
+
+TEST_F(PolicyStatisticsCollectorTest, MultiplePolicies) {
+  SetPolicy(kTestPolicy1);
+  SetPolicy(kTestPolicy2);
+
+  prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+                  (base::Time::Now() - update_delay_).ToInternalValue());
+
+  EXPECT_CALL(*policy_statistics_collector_.get(),
+              RecordPolicyUse(kTestPolicy1Id));
+  EXPECT_CALL(*policy_statistics_collector_.get(),
+              RecordPolicyUse(kTestPolicy2Id));
+
+  policy_statistics_collector_->Initialize();
+  EXPECT_EQ(1u, task_runner_->GetPendingTasks().size());
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_test_utils.cc b/components/policy/core/common/policy_test_utils.cc
new file mode 100644
index 0000000..b80867a
--- /dev/null
+++ b/components/policy/core/common/policy_test_utils.cc
@@ -0,0 +1,139 @@
+// Copyright 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 "components/policy/core/common/policy_test_utils.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+namespace policy {
+
+PolicyDetailsMap::PolicyDetailsMap() {}
+
+PolicyDetailsMap::~PolicyDetailsMap() {}
+
+GetChromePolicyDetailsCallback PolicyDetailsMap::GetCallback() const {
+  return base::Bind(&PolicyDetailsMap::Lookup, base::Unretained(this));
+}
+
+void PolicyDetailsMap::SetDetails(const std::string& policy,
+                                  const PolicyDetails* details) {
+  map_[policy] = details;
+}
+
+const PolicyDetails* PolicyDetailsMap::Lookup(const std::string& policy) const {
+  PolicyDetailsMapping::const_iterator it = map_.find(policy);
+  return it == map_.end() ? NULL : it->second;
+}
+
+bool PolicyServiceIsEmpty(const PolicyService* service) {
+  const PolicyMap& map = service->GetPolicies(
+      PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+  if (!map.empty()) {
+    base::DictionaryValue dict;
+    for (PolicyMap::const_iterator it = map.begin(); it != map.end(); ++it)
+      dict.SetWithoutPathExpansion(it->first, it->second.value->DeepCopy());
+    LOG(WARNING) << "There are pre-existing policies in this machine: " << dict;
+  }
+  return map.empty();
+}
+
+}  // namespace policy
+
+std::ostream& operator<<(std::ostream& os,
+                         const policy::PolicyBundle& bundle) {
+  os << "{" << std::endl;
+  for (policy::PolicyBundle::const_iterator iter = bundle.begin();
+       iter != bundle.end(); ++iter) {
+    os << "  \"" << iter->first << "\": " << *iter->second << "," << std::endl;
+  }
+  os << "}";
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyScope scope) {
+  switch (scope) {
+    case policy::POLICY_SCOPE_USER: {
+      os << "POLICY_SCOPE_USER";
+      break;
+    }
+    case policy::POLICY_SCOPE_MACHINE: {
+      os << "POLICY_SCOPE_MACHINE";
+      break;
+    }
+    default: {
+      os << "POLICY_SCOPE_UNKNOWN(" << int(scope) << ")";
+    }
+  }
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyLevel level) {
+  switch (level) {
+    case policy::POLICY_LEVEL_RECOMMENDED: {
+      os << "POLICY_LEVEL_RECOMMENDED";
+      break;
+    }
+    case policy::POLICY_LEVEL_MANDATORY: {
+      os << "POLICY_LEVEL_MANDATORY";
+      break;
+    }
+    default: {
+      os << "POLICY_LEVEL_UNKNOWN(" << int(level) << ")";
+    }
+  }
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyDomain domain) {
+  switch (domain) {
+    case policy::POLICY_DOMAIN_CHROME: {
+      os << "POLICY_DOMAIN_CHROME";
+      break;
+    }
+    case policy::POLICY_DOMAIN_EXTENSIONS: {
+      os << "POLICY_DOMAIN_EXTENSIONS";
+      break;
+    }
+    default: {
+      os << "POLICY_DOMAIN_UNKNOWN(" << int(domain) << ")";
+    }
+  }
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap& policies) {
+  os << "{" << std::endl;
+  for (policy::PolicyMap::const_iterator iter = policies.begin();
+       iter != policies.end(); ++iter) {
+    os << "  \"" << iter->first << "\": " << iter->second << "," << std::endl;
+  }
+  os << "}";
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap::Entry& e) {
+  std::string value;
+  base::JSONWriter::WriteWithOptions(e.value,
+                                     base::JSONWriter::OPTIONS_PRETTY_PRINT,
+                                     &value);
+  os << "{" << std::endl
+     << "  \"level\": " << e.level << "," << std::endl
+     << "  \"scope\": " << e.scope << "," << std::endl
+     << "  \"value\": " << value
+     << "}";
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyNamespace& ns) {
+  os << ns.domain << "/" << ns.component_id;
+  return os;
+}
diff --git a/components/policy/core/common/policy_test_utils.h b/components/policy/core/common/policy_test_utils.h
new file mode 100644
index 0000000..e2c0c73
--- /dev/null
+++ b/components/policy/core/common/policy_test_utils.h
@@ -0,0 +1,60 @@
+// Copyright 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.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
+
+#include <map>
+#include <ostream>
+#include <string>
+
+#include "base/basictypes.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+class PolicyBundle;
+struct PolicyNamespace;
+
+// A mapping of policy names to PolicyDetails that can be used to set the
+// PolicyDetails for test policies.
+class PolicyDetailsMap {
+ public:
+  PolicyDetailsMap();
+  ~PolicyDetailsMap();
+
+  // The returned callback's lifetime is tied to |this| object.
+  GetChromePolicyDetailsCallback GetCallback() const;
+
+  // Does not take ownership of |details|.
+  void SetDetails(const std::string& policy, const PolicyDetails* details);
+
+ private:
+  typedef std::map<std::string, const PolicyDetails*> PolicyDetailsMapping;
+
+  const PolicyDetails* Lookup(const std::string& policy) const;
+
+  PolicyDetailsMapping map_;
+
+  DISALLOW_COPY_AND_ASSIGN(PolicyDetailsMap);
+};
+
+// Returns true if |service| is not serving any policies. Otherwise logs the
+// current policies and returns false.
+bool PolicyServiceIsEmpty(const PolicyService* service);
+
+}  // namespace policy
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyBundle& bundle);
+std::ostream& operator<<(std::ostream& os, policy::PolicyScope scope);
+std::ostream& operator<<(std::ostream& os, policy::PolicyLevel level);
+std::ostream& operator<<(std::ostream& os, policy::PolicyDomain domain);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap& policies);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap::Entry& e);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyNamespace& ns);
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
diff --git a/components/policy/core/common/schema_registry.h b/components/policy/core/common/schema_registry.h
index 5f56589..b9f7fa8 100644
--- a/components/policy/core/common/schema_registry.h
+++ b/components/policy/core/common/schema_registry.h
@@ -15,6 +15,7 @@
 #include "components/policy/core/common/policy_namespace.h"
 #include "components/policy/core/common/schema.h"
 #include "components/policy/core/common/schema_map.h"
+#include "components/policy/policy_export.h"
 
 namespace policy {