summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorglevin <glevin@chromium.org>2016-03-23 16:08:12 -0700
committerCommit bot <commit-bot@chromium.org>2016-03-23 23:10:03 +0000
commit5dd01a7d4eecdf094fad69b07040fdf9f7ce9721 (patch)
tree23194f091df4617f3881870036d6bacb8bd234f9
parent0a2a0e15f6741225fbce75cfc4fa07db0a00190d (diff)
downloadchromium_src-5dd01a7d4eecdf094fad69b07040fdf9f7ce9721.zip
chromium_src-5dd01a7d4eecdf094fad69b07040fdf9f7ce9721.tar.gz
chromium_src-5dd01a7d4eecdf094fad69b07040fdf9f7ce9721.tar.bz2
Creating a "Quirks Client" to download icc files and other display info
("quirks") from a Quirks Server. These provide display-specific tuning (e.g. gamma ramps) on a per-monitor basis. Each Quirks Client handles downloading and writing a single file. The single Quirks Manager handles external requests for file paths, creates clients when downloads are needed, and manages their life cycle. For more info, see go/cros-quirks-client-dd (particularly the "Code Design" section). BUG=549349 TEST=Start up device that has a an icc file on the Quirks Server, check that file is downloaded to /var/cache/display_profiles. At next startup, the gamma correction in the icc file should be applied to the display (this will only be visible to the degree that the gamma correction is large enough to be noticeable; the correct functioning of the Quirks Client is primarily determined by the appearance of the file). Review URL: https://codereview.chromium.org/1528963002 Cr-Commit-Position: refs/heads/master@{#382962}
-rw-r--r--ash/BUILD.gn3
-rw-r--r--ash/DEPS1
-rw-r--r--ash/ash.gyp2
-rw-r--r--ash/display/display_color_manager_chromeos.cc93
-rw-r--r--ash/display/display_color_manager_chromeos.h27
-rw-r--r--ash/display/display_color_manager_chromeos_unittest.cc120
-rw-r--r--chrome/app/chromeos_strings.grdp6
-rw-r--r--chrome/browser/about_flags.cc6
-rw-r--r--chrome/browser/chromeos/chrome_browser_main_chromeos.cc10
-rw-r--r--chrome/browser/chromeos/display/quirks_browsertest.cc139
-rw-r--r--chrome/browser/chromeos/display/quirks_manager_delegate_impl.cc62
-rw-r--r--chrome/browser/chromeos/display/quirks_manager_delegate_impl.h34
-rw-r--r--chrome/browser/chromeos/login/session/user_session_manager.cc4
-rw-r--r--chrome/browser/prefs/browser_prefs.cc4
-rw-r--r--chrome/chrome_browser_chromeos.gypi3
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chromeos/chromeos_paths.cc10
-rw-r--r--chromeos/chromeos_paths.h6
-rw-r--r--chromeos/chromeos_pref_names.cc4
-rw-r--r--chromeos/chromeos_pref_names.h1
-rw-r--r--components/OWNERS2
-rw-r--r--components/components.gyp1
-rw-r--r--components/quirks.gypi38
-rw-r--r--components/quirks/BUILD.gn27
-rw-r--r--components/quirks/DEPS5
-rw-r--r--components/quirks/OWNERS2
-rw-r--r--components/quirks/pref_names.cc15
-rw-r--r--components/quirks/pref_names.h16
-rw-r--r--components/quirks/quirks_client.cc171
-rw-r--r--components/quirks/quirks_client.h80
-rw-r--r--components/quirks/quirks_export.h29
-rw-r--r--components/quirks/quirks_manager.cc257
-rw-r--r--components/quirks/quirks_manager.h178
-rw-r--r--components/quirks/switches.cc14
-rw-r--r--components/quirks/switches.h20
-rw-r--r--tools/metrics/histograms/histograms.xml1
36 files changed, 1306 insertions, 86 deletions
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 31aaeba..9c7cdd0 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -105,6 +105,7 @@ component("ash") {
deps += [
"//chromeos",
"//chromeos:power_manager_proto",
+ "//components/quirks",
"//device/bluetooth",
# TODO(msw): Remove this; only ash_with_content should depend on webkit.
@@ -425,7 +426,9 @@ test("ash_unittests") {
"//chromeos",
"//chromeos:power_manager_proto",
"//chromeos:test_support_without_gmock",
+ "//components/quirks",
"//device/bluetooth",
+ "//net:net",
"//ui/chromeos:ui_chromeos",
"//ui/display",
"//ui/display:test_support",
diff --git a/ash/DEPS b/ash/DEPS
index 0e0ff3d..bbc8690 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -2,6 +2,7 @@ include_rules = [
"+device/bluetooth",
"+cc/debug",
"+chromeos",
+ "+components/quirks",
"+components/signin/core/account_id",
"+components/user_manager",
"+components/wallpaper",
diff --git a/ash/ash.gyp b/ash/ash.gyp
index e6d0ac8..3a8f474 100644
--- a/ash/ash.gyp
+++ b/ash/ash.gyp
@@ -1028,6 +1028,7 @@
'../chromeos/chromeos.gyp:chromeos',
# Ash #includes power_supply_properties.pb.h directly.
'../chromeos/chromeos.gyp:power_manager_proto',
+ '../components/components.gyp:quirks',
'../device/bluetooth/bluetooth.gyp:device_bluetooth',
'../third_party/qcms/qcms.gyp:qcms',
'../ui/chromeos/ui_chromeos.gyp:ui_chromeos_resources',
@@ -1230,6 +1231,7 @@
'dependencies': [
'../chromeos/chromeos.gyp:chromeos_test_support_without_gmock',
'../chromeos/chromeos.gyp:power_manager_proto',
+ '../components/components.gyp:quirks',
'../device/bluetooth/bluetooth.gyp:device_bluetooth',
'../ui/display/display.gyp:display',
'../ui/display/display.gyp:display_test_support',
diff --git a/ash/display/display_color_manager_chromeos.cc b/ash/display/display_color_manager_chromeos.cc
index 1241978..e555919 100644
--- a/ash/display/display_color_manager_chromeos.cc
+++ b/ash/display/display_color_manager_chromeos.cc
@@ -7,39 +7,27 @@
#include <utility>
#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/command_line.h"
-#include "base/files/file_path.h"
#include "base/files/file_util.h"
-#include "base/format_macros.h"
#include "base/logging.h"
-#include "base/message_loop/message_loop.h"
-#include "base/path_service.h"
#include "base/stl_util.h"
-#include "base/strings/stringprintf.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_worker_pool.h"
-#include "chromeos/chromeos_paths.h"
+#include "components/quirks/quirks_manager.h"
#include "third_party/qcms/src/qcms.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/gamma_ramp_rgb_entry.h"
-#include "ui/display/types/native_display_delegate.h"
#include "ui/gfx/display.h"
-#include "ui/gfx/screen.h"
namespace ash {
namespace {
-bool ParseFile(const base::FilePath& path,
- DisplayColorManager::ColorCalibrationData* data) {
- if (!base::PathExists(path)) // No icc file for this display; not an error.
- return false;
+scoped_ptr<DisplayColorManager::ColorCalibrationData> ParseDisplayProfile(
+ const base::FilePath& path) {
qcms_profile* display_profile = qcms_profile_from_path(path.value().c_str());
-
if (!display_profile) {
LOG(WARNING) << "Unable to load ICC file: " << path.value();
- return false;
+ return nullptr;
}
size_t vcgt_channel_length =
@@ -47,7 +35,7 @@ bool ParseFile(const base::FilePath& path,
if (!vcgt_channel_length) {
LOG(WARNING) << "No vcgt table in ICC file: " << path.value();
qcms_profile_release(display_profile);
- return false;
+ return nullptr;
}
std::vector<uint16_t> vcgt_data;
@@ -55,9 +43,11 @@ bool ParseFile(const base::FilePath& path,
if (!qcms_profile_get_vcgt_rgb_channels(display_profile, &vcgt_data[0])) {
LOG(WARNING) << "Unable to get vcgt data";
qcms_profile_release(display_profile);
- return false;
+ return nullptr;
}
+ scoped_ptr<DisplayColorManager::ColorCalibrationData> data(
+ new DisplayColorManager::ColorCalibrationData());
data->lut.resize(vcgt_channel_length);
for (size_t i = 0; i < vcgt_channel_length; ++i) {
data->lut[i].r = vcgt_data[i];
@@ -65,16 +55,8 @@ bool ParseFile(const base::FilePath& path,
data->lut[i].b = vcgt_data[(vcgt_channel_length * 2) + i];
}
qcms_profile_release(display_profile);
- return true;
-}
-
-base::FilePath PathForDisplaySnapshot(const ui::DisplaySnapshot* snapshot) {
- base::FilePath path;
- CHECK(
- PathService::Get(chromeos::DIR_DEVICE_COLOR_CALIBRATION_PROFILES, &path));
- path = path.Append(
- base::StringPrintf("%08" PRIx64 ".icc", snapshot->product_id()));
- return path;
+ VLOG(1) << "Gamma data successfully read from icc file";
+ return data;
}
} // namespace
@@ -82,7 +64,9 @@ base::FilePath PathForDisplaySnapshot(const ui::DisplaySnapshot* snapshot) {
DisplayColorManager::DisplayColorManager(
ui::DisplayConfigurator* configurator,
base::SequencedWorkerPool* blocking_pool)
- : configurator_(configurator), blocking_pool_(blocking_pool) {
+ : configurator_(configurator),
+ blocking_pool_(blocking_pool),
+ weak_ptr_factory_(this) {
configurator_->AddObserver(this);
}
@@ -114,33 +98,56 @@ void DisplayColorManager::ApplyDisplayColorCalibration(int64_t display_id,
void DisplayColorManager::LoadCalibrationForDisplay(
const ui::DisplaySnapshot* display) {
+ DCHECK(thread_checker_.CalledOnValidThread());
if (display->display_id() == gfx::Display::kInvalidDisplayID) {
LOG(WARNING) << "Trying to load calibration data for invalid display id";
return;
}
- base::FilePath path = PathForDisplaySnapshot(display);
+ quirks::QuirksManager::Get()->RequestIccProfilePath(
+ display->product_id(),
+ base::Bind(&DisplayColorManager::FinishLoadCalibrationForDisplay,
+ weak_ptr_factory_.GetWeakPtr(), display->display_id(),
+ display->product_id(), display->type()));
+}
+
+void DisplayColorManager::FinishLoadCalibrationForDisplay(
+ int64_t display_id,
+ int64_t product_id,
+ ui::DisplayConnectionType type,
+ const base::FilePath& path,
+ bool file_downloaded) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::string product_string = quirks::IdToHexString(product_id);
+ if (path.empty()) {
+ VLOG(1) << "No ICC file found with product id: " << product_string
+ << " for display id: " << display_id;
+ return;
+ }
+
+ if (file_downloaded && type == ui::DISPLAY_CONNECTION_TYPE_INTERNAL) {
+ VLOG(1) << "Downloaded ICC file with product id: " << product_string
+ << " for internal display id: " << display_id
+ << ". Profile will be applied on next startup.";
+ return;
+ }
+
VLOG(1) << "Loading ICC file " << path.value()
- << " for display id: " << display->display_id()
- << " with product id: " << display->product_id();
+ << " for display id: " << display_id
+ << " with product id: " << product_string;
- scoped_ptr<ColorCalibrationData> data(new ColorCalibrationData());
- base::Callback<bool(void)> request(
- base::Bind(&ParseFile, path, base::Unretained(data.get())));
base::PostTaskAndReplyWithResult(
- blocking_pool_, FROM_HERE, request,
- base::Bind(&DisplayColorManager::UpdateCalibrationData, AsWeakPtr(),
- display->display_id(), display->product_id(),
- base::Passed(std::move(data))));
+ blocking_pool_, FROM_HERE, base::Bind(&ParseDisplayProfile, path),
+ base::Bind(&DisplayColorManager::UpdateCalibrationData,
+ weak_ptr_factory_.GetWeakPtr(), display_id, product_id));
}
void DisplayColorManager::UpdateCalibrationData(
int64_t display_id,
int64_t product_id,
- scoped_ptr<ColorCalibrationData> data,
- bool success) {
- DCHECK_EQ(base::MessageLoop::current()->type(), base::MessageLoop::TYPE_UI);
- if (success) {
+ scoped_ptr<ColorCalibrationData> data) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (data) {
// The map takes over ownership of the underlying memory.
calibration_map_[product_id] = data.release();
ApplyDisplayColorCalibration(display_id, product_id);
diff --git a/ash/display/display_color_manager_chromeos.h b/ash/display/display_color_manager_chromeos.h
index b434e05..58e2d9c 100644
--- a/ash/display/display_color_manager_chromeos.h
+++ b/ash/display/display_color_manager_chromeos.h
@@ -14,15 +14,16 @@
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
#include "ui/display/chromeos/display_configurator.h"
-#include "ui/gfx/display.h"
-#include "ui/gfx/display_observer.h"
+#include "ui/display/types/display_constants.h"
namespace base {
class SequencedWorkerPool;
}
namespace ui {
+class DisplaySnapshot;
struct GammaRampRGBEntry;
} // namespace ui
@@ -31,8 +32,7 @@ namespace ash {
// An object that observes changes in display configuration applies any color
// calibration where needed.
class ASH_EXPORT DisplayColorManager
- : public ui::DisplayConfigurator::Observer,
- public base::SupportsWeakPtr<DisplayColorManager> {
+ : public ui::DisplayConfigurator::Observer {
public:
DisplayColorManager(ui::DisplayConfigurator* configurator,
base::SequencedWorkerPool* blocking_pool);
@@ -52,19 +52,28 @@ class ASH_EXPORT DisplayColorManager
std::vector<ui::GammaRampRGBEntry> lut;
};
+ protected:
+ virtual void FinishLoadCalibrationForDisplay(int64_t display_id,
+ int64_t product_id,
+ ui::DisplayConnectionType type,
+ const base::FilePath& path,
+ bool file_downloaded);
+ virtual void UpdateCalibrationData(int64_t display_id,
+ int64_t product_id,
+ scoped_ptr<ColorCalibrationData> data);
+
private:
void ApplyDisplayColorCalibration(int64_t display_id, int64_t product_id);
void LoadCalibrationForDisplay(const ui::DisplaySnapshot* display);
- void UpdateCalibrationData(
- int64_t display_id,
- int64_t product_id,
- scoped_ptr<DisplayColorManager::ColorCalibrationData> data,
- bool success);
ui::DisplayConfigurator* configurator_;
std::map<int64_t, ColorCalibrationData*> calibration_map_;
+ base::ThreadChecker thread_checker_;
base::SequencedWorkerPool* blocking_pool_;
+ // Factory for callbacks.
+ base::WeakPtrFactory<DisplayColorManager> weak_ptr_factory_;
+
DISALLOW_COPY_AND_ASSIGN(DisplayColorManager);
};
diff --git a/ash/display/display_color_manager_chromeos_unittest.cc b/ash/display/display_color_manager_chromeos_unittest.cc
index 081042e..f99db71 100644
--- a/ash/display/display_color_manager_chromeos_unittest.cc
+++ b/ash/display/display_color_manager_chromeos_unittest.cc
@@ -13,6 +13,8 @@
#include "base/test/scoped_path_override.h"
#include "base/test/sequenced_worker_pool_owner.h"
#include "chromeos/chromeos_paths.h"
+#include "components/quirks/quirks_manager.h"
+#include "net/url_request/url_request_context_getter.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/chromeos/test/action_logger_util.h"
#include "ui/display/chromeos/test/test_display_snapshot.h"
@@ -22,30 +24,79 @@ namespace ash {
namespace {
-// Monitors if any task is processed by the message loop.
-class TaskObserver : public base::MessageLoop::TaskObserver {
+const char kSetGammaAction[] =
+ "set_gamma_ramp(id=123,rgb[0]*rgb[255]=???????????\?)";
+
+class DisplayColorManagerForTest : public DisplayColorManager {
public:
- TaskObserver() { base::MessageLoop::current()->AddTaskObserver(this); }
- ~TaskObserver() override {
- base::MessageLoop::current()->RemoveTaskObserver(this);
- }
+ DisplayColorManagerForTest(ui::DisplayConfigurator* configurator,
+ base::SequencedWorkerPool* blocking_pool)
+ : DisplayColorManager(configurator, blocking_pool) {}
- // MessageLoop::TaskObserver overrides.
- void WillProcessTask(const base::PendingTask& pending_task) override {}
- void DidProcessTask(const base::PendingTask& pending_task) override {
- base::MessageLoop::current()->QuitWhenIdle();
+ void SetOnFinishedForTest(base::Closure on_finished_for_test) {
+ on_finished_for_test_ = on_finished_for_test;
}
private:
- DISALLOW_COPY_AND_ASSIGN(TaskObserver);
+ void FinishLoadCalibrationForDisplay(int64_t display_id,
+ int64_t product_id,
+ ui::DisplayConnectionType type,
+ const base::FilePath& path,
+ bool file_downloaded) override {
+ DisplayColorManager::FinishLoadCalibrationForDisplay(
+ display_id, product_id, type, path, file_downloaded);
+ // If path is empty, there is no icc file, and the DCM's work is done.
+ if (path.empty() && !on_finished_for_test_.is_null()) {
+ on_finished_for_test_.Run();
+ on_finished_for_test_.Reset();
+ }
+ }
+
+ void UpdateCalibrationData(int64_t display_id,
+ int64_t product_id,
+ scoped_ptr<ColorCalibrationData> data) override {
+ DisplayColorManager::UpdateCalibrationData(display_id, product_id,
+ std::move(data));
+ if (!on_finished_for_test_.is_null()) {
+ on_finished_for_test_.Run();
+ on_finished_for_test_.Reset();
+ }
+ }
+
+ base::Closure on_finished_for_test_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayColorManagerForTest);
};
-// Run at least one task. WARNING: Will not return unless there is at least one
-// task to be processed.
-void RunBlockingPoolTask() {
- TaskObserver task_observer;
- base::RunLoop().Run();
-}
+// Implementation of QuirksManager::Delegate to fake chrome-restricted parts.
+class QuirksManagerDelegateTestImpl : public quirks::QuirksManager::Delegate {
+ public:
+ QuirksManagerDelegateTestImpl(base::FilePath color_path)
+ : color_path_(color_path) {}
+
+ // Unused by these tests.
+ std::string GetApiKey() const override { return std::string(); }
+
+ base::FilePath GetBuiltInDisplayProfileDirectory() const override {
+ return color_path_;
+ }
+
+ // Unused by these tests.
+ base::FilePath GetDownloadDisplayProfileDirectory() const override {
+ return base::FilePath();
+ }
+
+ // Unused by these tests.
+ void GetDaysSinceOobe(
+ quirks::QuirksManager::DaysSinceOobeCallback callback) const override {}
+
+ private:
+ ~QuirksManagerDelegateTestImpl() override = default;
+
+ base::FilePath color_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuirksManagerDelegateTestImpl);
+};
} // namespace
@@ -61,8 +112,8 @@ class DisplayColorManagerTest : public testing::Test {
configurator_.SetDelegateForTesting(
scoped_ptr<ui::NativeDisplayDelegate>(native_display_delegate_));
- color_manager_.reset(
- new DisplayColorManager(&configurator_, pool_owner_->pool().get()));
+ color_manager_.reset(new DisplayColorManagerForTest(
+ &configurator_, pool_owner_->pool().get()));
EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &color_path_));
@@ -71,9 +122,23 @@ class DisplayColorManagerTest : public testing::Test {
.Append(FILE_PATH_LITERAL("test_data"));
path_override_.reset(new base::ScopedPathOverride(
chromeos::DIR_DEVICE_COLOR_CALIBRATION_PROFILES, color_path_));
+
+ quirks::QuirksManager::Initialize(
+ scoped_ptr<quirks::QuirksManager::Delegate>(
+ new QuirksManagerDelegateTestImpl(color_path_)),
+ pool_owner_->pool().get(), nullptr, nullptr);
+ }
+
+ void TearDown() override {
+ quirks::QuirksManager::Shutdown();
+ pool_owner_->pool()->Shutdown();
}
- void TearDown() override { pool_owner_->pool()->Shutdown(); }
+ void WaitOnColorCalibration() {
+ base::RunLoop run_loop;
+ color_manager_->SetOnFinishedForTest(run_loop.QuitClosure());
+ run_loop.Run();
+ }
DisplayColorManagerTest() : test_api_(&configurator_) {}
~DisplayColorManagerTest() override {}
@@ -85,7 +150,7 @@ class DisplayColorManagerTest : public testing::Test {
ui::DisplayConfigurator configurator_;
ui::DisplayConfigurator::TestApi test_api_;
ui::test::TestNativeDisplayDelegate* native_display_delegate_; // not owned
- scoped_ptr<DisplayColorManager> color_manager_;
+ scoped_ptr<DisplayColorManagerForTest> color_manager_;
base::MessageLoopForUI ui_message_loop_;
scoped_ptr<base::SequencedWorkerPoolOwner> pool_owner_;
@@ -114,13 +179,10 @@ TEST_F(DisplayColorManagerTest, VCGTOnly) {
configurator_.OnConfigurationChanged();
EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
- log_->GetActionsAndClear();
-
- RunBlockingPoolTask();
- EXPECT_TRUE(base::MatchPattern(
- log_->GetActionsAndClear(),
- "set_gamma_ramp(id=123,rgb[0]*rgb[255]=???????????\?)"));
+ log_->GetActionsAndClear();
+ WaitOnColorCalibration();
+ EXPECT_TRUE(base::MatchPattern(log_->GetActionsAndClear(), kSetGammaAction));
}
TEST_F(DisplayColorManagerTest, NoMatchProductID) {
@@ -145,7 +207,7 @@ TEST_F(DisplayColorManagerTest, NoMatchProductID) {
EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
log_->GetActionsAndClear();
- RunBlockingPoolTask();
+ WaitOnColorCalibration();
EXPECT_STREQ("", log_->GetActionsAndClear().c_str());
}
@@ -171,7 +233,7 @@ TEST_F(DisplayColorManagerTest, NoVCGT) {
EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
log_->GetActionsAndClear();
- RunBlockingPoolTask();
+ WaitOnColorCalibration();
EXPECT_STREQ("", log_->GetActionsAndClear().c_str());
}
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index c88d18e..61ad9c7 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -1239,6 +1239,12 @@ Press any key to continue exploring.
<message name="IDS_FLAGS_DISPLAY_COLOR_CALIBRATION_DESCRIPTION" desc="Description for the flag for the color calibration of the display.">
Allow color calibration of the display if the display supports the feature.
</message>
+ <message name="IDS_FLAGS_ENABLE_QUIRKS_CLIENT_NAME" desc="Name for the flag to enable the Quirks Client for calibration of the display and other hardware.">
+ Enable Quirks Client for display calibration.
+ </message>
+ <message name="IDS_FLAGS_ENABLE_QUIRKS_CLIENT_DESCRIPTION" desc="Description for the flag to enable the Quirks Client for calibration of the display and other hardware.">
+ Enable retrieval of icc display files from Quirks Server for display color calibration.
+ </message>
<message name="IDS_FLAGS_MEMORY_PRESSURE_THRESHOLD_NAME" desc="Name for the flag which specifies which memory pressure strategy should be used on ChromeOS.">
Memory discard strategy for advanced pressure handling
</message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index df060fb..713c62d 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -48,6 +48,7 @@
#include "components/omnibox/browser/omnibox_switches.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/proximity_auth/switches.h"
+#include "components/quirks/switches.h"
#include "components/search/search_switches.h"
#include "components/security_state/switches.h"
#include "components/signin/core/common/signin_switches.h"
@@ -837,6 +838,11 @@ const FeatureEntry kFeatureEntries[] = {
ui::switches::kDisableDisplayColorCalibration),
},
{
+ "enable-quirks-client", IDS_FLAGS_ENABLE_QUIRKS_CLIENT_NAME,
+ IDS_FLAGS_ENABLE_QUIRKS_CLIENT_DESCRIPTION, kOsCrOS,
+ SINGLE_VALUE_TYPE(quirks::switches::kEnableQuirksClient),
+ },
+ {
"ash-disable-screen-orientation-lock",
IDS_FLAGS_ASH_SCREEN_ORIENTATION_LOCK_NAME,
IDS_FLAGS_ASH_SCREEN_ORIENTATION_LOCK_DESCRIPTION, kOsCrOS,
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 1b571b8..f06a1e5 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -39,6 +39,7 @@
#include "chrome/browser/chromeos/dbus/chrome_proxy_resolver_delegate.h"
#include "chrome/browser/chromeos/dbus/kiosk_info_service_provider.h"
#include "chrome/browser/chromeos/dbus/screen_lock_service_provider.h"
+#include "chrome/browser/chromeos/display/quirks_manager_delegate_impl.h"
#include "chrome/browser/chromeos/events/event_rewriter.h"
#include "chrome/browser/chromeos/events/event_rewriter_controller.h"
#include "chrome/browser/chromeos/events/keyboard_driven_event_rewriter.h"
@@ -399,6 +400,13 @@ void ChromeBrowserMainPartsChromeos::PreMainMessageLoopRun() {
CrasAudioHandler::Initialize(
new AudioDevicesPrefHandlerImpl(g_browser_process->local_state()));
+ quirks::QuirksManager::Initialize(
+ scoped_ptr<quirks::QuirksManager::Delegate>(
+ new quirks::QuirksManagerDelegateImpl()),
+ content::BrowserThread::GetBlockingPool(),
+ g_browser_process->local_state(),
+ g_browser_process->system_request_context());
+
// Start loading machine statistics here. StatisticsProvider::Shutdown()
// will ensure that loading is aborted on early exit.
bool load_oem_statistics = !StartupUtils::IsOobeCompleted();
@@ -848,6 +856,8 @@ void ChromeBrowserMainPartsChromeos::PostMainMessageLoopRun() {
// Shutdown after PostMainMessageLoopRun() which should destroy all observers.
CrasAudioHandler::Shutdown();
+ quirks::QuirksManager::Shutdown();
+
// Called after
// ChromeBrowserMainPartsLinux::PostMainMessageLoopRun() to be
// executed after execution of chrome::CloseAsh(), because some
diff --git a/chrome/browser/chromeos/display/quirks_browsertest.cc b/chrome/browser/chromeos/display/quirks_browsertest.cc
new file mode 100644
index 0000000..93f86b8
--- /dev/null
+++ b/chrome/browser/chromeos/display/quirks_browsertest.cc
@@ -0,0 +1,139 @@
+// Copyright 2016 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/command_line.h"
+#include "base/files/file_util.h"
+#include "base/run_loop.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/quirks/quirks_manager.h"
+#include "components/quirks/switches.h"
+#include "content/public/test/test_utils.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+
+namespace quirks {
+
+namespace {
+
+const char kFakeIccJson[] = "{\n \"icc\": \"AAAIkCAgICACEAAA\"\n}";
+const char kFakeIccData[] = {0x00, 0x00, 0x08, 0x90, 0x20, 0x20,
+ 0x20, 0x20, 0x02, 0x10, 0x00, 0x00};
+
+// Create FakeURLFetcher which returns fake icc file json.
+scoped_ptr<net::URLFetcher> CreateFakeURLFetcherSuccess(
+ const GURL& url,
+ net::URLFetcherDelegate* delegate) {
+ net::URLFetcher* fetcher =
+ new net::FakeURLFetcher(url, delegate, kFakeIccJson, net::HTTP_OK,
+ net::URLRequestStatus::SUCCESS);
+ return make_scoped_ptr(fetcher);
+}
+
+// Create FakeURLFetcher which replies with HTTP_NOT_FOUND.
+scoped_ptr<net::URLFetcher> CreateFakeURLFetcherFailure(
+ const GURL& url,
+ net::URLFetcherDelegate* delegate) {
+ net::URLFetcher* fetcher = new net::FakeURLFetcher(
+ url, delegate, "File not found", net::HTTP_NOT_FOUND,
+ net::URLRequestStatus::FAILED);
+ return make_scoped_ptr(fetcher);
+}
+
+// Full path to fake icc file in <tmp test directory>/display_profiles/.
+base::FilePath GetPathForIccFile(int64_t product_id) {
+ return QuirksManager::Get()
+ ->delegate()
+ ->GetDownloadDisplayProfileDirectory()
+ .Append(quirks::IdToFileName(product_id));
+}
+
+} // namespace
+
+class QuirksBrowserTest : public InProcessBrowserTest {
+ public:
+ QuirksBrowserTest() : file_existed_(false) {}
+
+ protected:
+ ~QuirksBrowserTest() override = default;
+
+ void Initialize() {
+ // NOTE: QuirksManager::Initialize() isn't necessary here, since it'll be
+ // called in ChromeBrowserMainPartsChromeos::PreMainMessageLoopRun().
+
+ // Create display_profiles subdirectory under temp profile directory.
+ const base::FilePath path =
+ QuirksManager::Get()->delegate()->GetDownloadDisplayProfileDirectory();
+ base::File::Error error = base::File::FILE_OK;
+ bool created = base::CreateDirectoryAndGetError(path, &error);
+ ASSERT_TRUE(created);
+
+ // Quirks clients can't run until after login.
+ quirks::QuirksManager::Get()->OnLoginCompleted();
+ }
+
+ // Query QuirksManager for icc file, then run msg loop to wait for callback.
+ // |find_fake_file| indicates that URLFetcher should respond with success.
+ void TestQuirksClient(int64_t product_id, bool find_fake_file) {
+ // Set up fake url getter.
+ QuirksManager::Get()->SetFakeQuirksFetcherCreatorForTests(
+ base::Bind(find_fake_file ? &CreateFakeURLFetcherSuccess
+ : &CreateFakeURLFetcherFailure));
+
+ base::RunLoop run_loop;
+ end_message_loop_ = run_loop.QuitClosure();
+
+ quirks::QuirksManager::Get()->RequestIccProfilePath(
+ product_id, base::Bind(&QuirksBrowserTest::OnQuirksClientFinished,
+ base::Unretained(this)));
+
+ run_loop.Run();
+ }
+
+ // Callback from RequestIccProfilePath().
+ void OnQuirksClientFinished(const base::FilePath& path, bool downloaded) {
+ icc_path_ = path;
+ file_existed_ = !downloaded;
+ ASSERT_TRUE(!end_message_loop_.is_null());
+ end_message_loop_.Run();
+ }
+
+ // InProcessBrowserTest overrides.
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kEnableQuirksClient);
+ }
+
+ void SetUpOnMainThread() override { Initialize(); }
+
+ base::Closure end_message_loop_; // Callback to terminate message loop.
+ base::FilePath icc_path_; // Path to icc file if found or downloaded.
+ bool file_existed_; // File was previously downloaded.
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuirksBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(QuirksBrowserTest, DownloadIccFile) {
+ // Request a file from fake Quirks Server, verify that file is written with
+ // correct location and data.
+ TestQuirksClient(0x0000aaaa, true);
+ base::FilePath path = GetPathForIccFile(0x0000aaaa);
+ EXPECT_EQ(icc_path_, path);
+ EXPECT_EQ(file_existed_, false);
+ EXPECT_TRUE(base::PathExists(path));
+ char data[32];
+ ReadFile(path, data, sizeof(data));
+ EXPECT_EQ(0, memcmp(data, kFakeIccData, sizeof(kFakeIccData)));
+
+ // Retest same file, this time expect it to already exist.
+ TestQuirksClient(0x0000aaaa, true);
+ EXPECT_EQ(icc_path_, path);
+ EXPECT_EQ(file_existed_, true);
+
+ // Finally, request a file that doesn't exist on fake Quirks Server.
+ TestQuirksClient(0x1111bbbb, false);
+ EXPECT_EQ(icc_path_, base::FilePath());
+ EXPECT_EQ(file_existed_, false);
+ EXPECT_FALSE(base::PathExists(GetPathForIccFile(0x1111bbbb)));
+}
+
+} // namespace quirks
diff --git a/chrome/browser/chromeos/display/quirks_manager_delegate_impl.cc b/chrome/browser/chromeos/display/quirks_manager_delegate_impl.cc
new file mode 100644
index 0000000..f4d51e4
--- /dev/null
+++ b/chrome/browser/chromeos/display/quirks_manager_delegate_impl.cc
@@ -0,0 +1,62 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/display/quirks_manager_delegate_impl.h"
+
+#include "base/path_service.h"
+#include "base/sys_info.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/chromeos/login/startup_utils.h"
+#include "chrome/common/chrome_paths.h"
+#include "chromeos/chromeos_paths.h"
+#include "content/public/browser/browser_thread.h"
+#include "google_apis/google_api_keys.h"
+
+namespace {
+
+const char kUserDataDisplayProfilesDirectory[] = "display_profiles";
+
+int GetDaysSinceOobeOnBlockingPool() {
+ return chromeos::StartupUtils::GetTimeSinceOobeFlagFileCreation().InDays();
+}
+
+} // namespace
+
+namespace quirks {
+
+std::string QuirksManagerDelegateImpl::GetApiKey() const {
+ return google_apis::GetAPIKey();
+}
+
+base::FilePath QuirksManagerDelegateImpl::GetBuiltInDisplayProfileDirectory()
+ const {
+ base::FilePath path;
+ if (!PathService::Get(chromeos::DIR_DEVICE_COLOR_CALIBRATION_PROFILES, &path))
+ LOG(ERROR) << "Could not get system path for display calibration profiles.";
+ return path;
+}
+
+// On chrome device, returns /var/cache/display_profiles.
+// On Linux desktop, returns {DIR_USER_DATA}/display_profiles.
+base::FilePath QuirksManagerDelegateImpl::GetDownloadDisplayProfileDirectory()
+ const {
+ base::FilePath directory;
+ if (base::SysInfo::IsRunningOnChromeOS()) {
+ PathService::Get(chromeos::DIR_DEVICE_DISPLAY_PROFILES, &directory);
+ } else {
+ PathService::Get(chrome::DIR_USER_DATA, &directory);
+ directory = directory.Append(kUserDataDisplayProfilesDirectory);
+ }
+ return directory;
+}
+
+void QuirksManagerDelegateImpl::GetDaysSinceOobe(
+ QuirksManager::DaysSinceOobeCallback callback) const {
+ base::PostTaskAndReplyWithResult(
+ content::BrowserThread::GetBlockingPool(), FROM_HERE,
+ base::Bind(&GetDaysSinceOobeOnBlockingPool), callback);
+}
+
+} // namespace quirks
diff --git a/chrome/browser/chromeos/display/quirks_manager_delegate_impl.h b/chrome/browser/chromeos/display/quirks_manager_delegate_impl.h
new file mode 100644
index 0000000..a6b1394
--- /dev/null
+++ b/chrome/browser/chromeos/display/quirks_manager_delegate_impl.h
@@ -0,0 +1,34 @@
+// Copyright 2016 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 CHROME_BROWSER_CHROMEOS_DISPLAY_QUIRKS_MANAGER_DELEGATE_IMPL_H_
+#define CHROME_BROWSER_CHROMEOS_DISPLAY_QUIRKS_MANAGER_DELEGATE_IMPL_H_
+
+#include "base/macros.h"
+#include "components/quirks/quirks_manager.h"
+
+namespace quirks {
+
+// Working implementation of QuirksManager::Delegate for access to chrome-
+// restricted parts.
+class QuirksManagerDelegateImpl : public QuirksManager::Delegate {
+ public:
+ QuirksManagerDelegateImpl() = default;
+
+ // QuirksManager::Delegate implementation.
+ std::string GetApiKey() const override;
+ base::FilePath GetBuiltInDisplayProfileDirectory() const override;
+ base::FilePath GetDownloadDisplayProfileDirectory() const override;
+ void GetDaysSinceOobe(
+ QuirksManager::DaysSinceOobeCallback callback) const override;
+
+ private:
+ ~QuirksManagerDelegateImpl() override = default;
+
+ DISALLOW_COPY_AND_ASSIGN(QuirksManagerDelegateImpl);
+};
+
+} // namespace quirks
+
+#endif // CHROME_BROWSER_CHROMEOS_DISPLAY_QUIRKS_MANAGER_DELEGATE_IMPL_H_
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index 710e365..2a75b4c 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -99,6 +99,7 @@
#include "components/prefs/pref_member.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
+#include "components/quirks/quirks_manager.h"
#include "components/session_manager/core/session_manager.h"
#include "components/signin/core/account_id/account_id.h"
#include "components/signin/core/browser/account_tracker_service.h"
@@ -1177,6 +1178,9 @@ void UserSessionManager::FinalizePrepareProfile(Profile* profile) {
// launch browser.
bool browser_launched = InitializeUserSession(profile);
+ // Only allow Quirks downloads after login is finished.
+ quirks::QuirksManager::Get()->OnLoginCompleted();
+
// If needed, create browser observer to display first run OOBE Goodies page.
first_run::GoodiesDisplayer::Init();
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 4692ca5..39015ae 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -195,6 +195,7 @@
#include "chromeos/audio/audio_devices_pref_handler_impl.h"
#include "chromeos/timezone/timezone_resolver.h"
#include "components/invalidation/impl/invalidator_storage.h"
+#include "components/quirks/quirks_manager.h"
#else
#include "chrome/browser/extensions/default_apps.h"
#endif
@@ -371,6 +372,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) {
chromeos::StartupUtils::RegisterPrefs(registry);
chromeos::system::AutomaticRebootManager::RegisterPrefs(registry);
chromeos::system::InputDeviceSettings::RegisterPrefs(registry);
+ chromeos::TimeZoneResolver::RegisterPrefs(registry);
chromeos::UserImageManager::RegisterPrefs(registry);
chromeos::UserSessionManager::RegisterPrefs(registry);
chromeos::WallpaperManager::RegisterPrefs(registry);
@@ -383,7 +385,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) {
policy::DeviceCloudPolicyManagerChromeOS::RegisterPrefs(registry);
policy::DeviceStatusCollector::RegisterPrefs(registry);
policy::PolicyCertServiceFactory::RegisterPrefs(registry);
- chromeos::TimeZoneResolver::RegisterPrefs(registry);
+ quirks::QuirksManager::RegisterPrefs(registry);
#endif
#if defined(OS_MACOSX)
diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi
index b7d3609..51b6575 100644
--- a/chrome/chrome_browser_chromeos.gypi
+++ b/chrome/chrome_browser_chromeos.gypi
@@ -135,6 +135,8 @@
'browser/chromeos/display/output_protection_delegate.h',
'browser/chromeos/display/overscan_calibrator.cc',
'browser/chromeos/display/overscan_calibrator.h',
+ 'browser/chromeos/display/quirks_manager_delegate_impl.cc',
+ 'browser/chromeos/display/quirks_manager_delegate_impl.h',
'browser/chromeos/drive/debug_info_collector.cc',
'browser/chromeos/drive/debug_info_collector.h',
'browser/chromeos/drive/download_handler.cc',
@@ -1162,6 +1164,7 @@
'../components/components.gyp:pairing',
'../components/components.gyp:policy',
'../components/components.gyp:proxy_config',
+ '../components/components.gyp:quirks',
'../components/components.gyp:ssl_config',
'../components/components.gyp:user_manager',
# This depends directly on the variations target, rather than just
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index a907484..e3ee6a3 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -706,6 +706,7 @@
'browser/chromeos/customization/customization_document_browsertest.cc',
'browser/chromeos/customization/customization_wallpaper_downloader_browsertest.cc',
'browser/chromeos/device/input_service_proxy_browsertest.cc',
+ 'browser/chromeos/display/quirks_browsertest.cc',
'browser/chromeos/drive/drive_integration_service_browsertest.cc',
'browser/chromeos/extensions/accessibility_features_apitest.cc',
'browser/chromeos/extensions/echo_private_apitest.cc',
diff --git a/chromeos/chromeos_paths.cc b/chromeos/chromeos_paths.cc
index 64005cc..58c4f20 100644
--- a/chromeos/chromeos_paths.cc
+++ b/chromeos/chromeos_paths.cc
@@ -47,12 +47,15 @@ const base::FilePath::CharType kDeviceLocalAccountExternalDataDir[] =
const base::FilePath::CharType kDeviceLocalAccountComponentPolicy[] =
FILE_PATH_LITERAL("/var/cache/device_local_account_component_policy");
-const base::FilePath::CharType kDeviceExtensionLocalCache[] =
- FILE_PATH_LITERAL("/var/cache/external_cache");
+const base::FilePath::CharType kDeviceDisplayProfileDirectory[] =
+ FILE_PATH_LITERAL("/var/cache/display_profiles");
const base::FilePath::CharType kDeviceColorProfileDirectory[] =
FILE_PATH_LITERAL("/usr/share/color/icc");
+const base::FilePath::CharType kDeviceExtensionLocalCache[] =
+ FILE_PATH_LITERAL("/var/cache/external_cache");
+
bool PathProvider(int key, base::FilePath* result) {
switch (key) {
case FILE_DEFAULT_APP_ORDER:
@@ -85,6 +88,9 @@ bool PathProvider(int key, base::FilePath* result) {
case DIR_DEVICE_LOCAL_ACCOUNT_COMPONENT_POLICY:
*result = base::FilePath(kDeviceLocalAccountComponentPolicy);
break;
+ case DIR_DEVICE_DISPLAY_PROFILES:
+ *result = base::FilePath(kDeviceDisplayProfileDirectory);
+ break;
case DIR_DEVICE_COLOR_CALIBRATION_PROFILES:
*result = base::FilePath(kDeviceColorProfileDirectory);
break;
diff --git a/chromeos/chromeos_paths.h b/chromeos/chromeos_paths.h
index 95fdcfa..297e68a 100644
--- a/chromeos/chromeos_paths.h
+++ b/chromeos/chromeos_paths.h
@@ -44,8 +44,10 @@ enum {
// device-local accounts.
// Currently this is used for
// policy for extensions.
- DIR_DEVICE_COLOR_CALIBRATION_PROFILES, // Directory where system color
- // calibration files can be found.
+ DIR_DEVICE_DISPLAY_PROFILES, // Destination directory for system display
+ // profiles downloaded from Quirks Server.
+ DIR_DEVICE_COLOR_CALIBRATION_PROFILES, // Directory where system color
+ // calibration files can be found.
DIR_DEVICE_EXTENSION_LOCAL_CACHE, // Directory where extension local cache
// is stored.
PATH_END
diff --git a/chromeos/chromeos_pref_names.cc b/chromeos/chromeos_pref_names.cc
index aaa9120..a7b905d 100644
--- a/chromeos/chromeos_pref_names.cc
+++ b/chromeos/chromeos_pref_names.cc
@@ -38,6 +38,10 @@ const char kAudioVolumePercent[] = "settings.audio.volume_percent";
// as the active one for audio I/O, or it's a new plugged device.
const char kAudioDevicesState[] = "settings.audio.device_state";
+// A dictionary of info for Quirks Client/Server interaction, mostly last server
+// request times, keyed to display product_id's.
+const char kQuirksClientLastServerCheck[] = "quirks_client.last_server_check";
+
} // namespace prefs
} // namespace chromeos
diff --git a/chromeos/chromeos_pref_names.h b/chromeos/chromeos_pref_names.h
index adb8aac..db86a4f 100644
--- a/chromeos/chromeos_pref_names.h
+++ b/chromeos/chromeos_pref_names.h
@@ -16,6 +16,7 @@ CHROMEOS_EXPORT extern const char kAudioMute[];
CHROMEOS_EXPORT extern const char kAudioOutputAllowed[];
CHROMEOS_EXPORT extern const char kAudioVolumePercent[];
CHROMEOS_EXPORT extern const char kAudioDevicesState[];
+CHROMEOS_EXPORT extern const char kQuirksClientLastServerCheck[];
} // namespace prefs
} // namespace chromeos
diff --git a/components/OWNERS b/components/OWNERS
index 48e9069..66fc1e5 100644
--- a/components/OWNERS
+++ b/components/OWNERS
@@ -153,6 +153,8 @@ per-file proxy_config.gypi=file://components/proxy_config/OWNERS
per-file query_parser.gypi=file://components/query_parser/OWNERS
+per-file quirks.gypi=file://components/quirks/OWNERS
+
per-file rappor.gypi=file://components/rappor/OWNERS
per-file renderer_context_menu.gypi=file://components/renderer_context_menu/OWNERS
diff --git a/components/components.gyp b/components/components.gyp
index e05f3935..fd1bf30 100644
--- a/components/components.gyp
+++ b/components/components.gyp
@@ -155,6 +155,7 @@
'includes': [
'arc.gypi',
'pairing.gypi',
+ 'quirks.gypi',
'timers.gypi',
'wifi_sync.gypi',
],
diff --git a/components/quirks.gypi b/components/quirks.gypi
new file mode 100644
index 0000000..5dd5cfb
--- /dev/null
+++ b/components/quirks.gypi
@@ -0,0 +1,38 @@
+# Copyright 2016 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.
+
+{
+ 'targets': [
+ {
+ # GN version: //components/quirks
+ 'target_name': 'quirks',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../components/prefs/prefs.gyp:prefs',
+ '../net/net.gyp:net',
+ '../url/url.gyp:url_lib',
+ 'version_info',
+ ],
+ 'include_dirs': [
+ '..',
+ ],
+ 'defines': [
+ 'QUIRKS_IMPLEMENTATION',
+ ],
+ 'sources': [
+ # Note: sources list duplicated in GN build.
+ 'quirks/pref_names.cc',
+ 'quirks/pref_names.h',
+ 'quirks/quirks_client.cc',
+ 'quirks/quirks_client.h',
+ 'quirks/quirks_export.h',
+ 'quirks/quirks_manager.cc',
+ 'quirks/quirks_manager.h',
+ 'quirks/switches.cc',
+ 'quirks/switches.h',
+ ],
+ },
+ ],
+}
diff --git a/components/quirks/BUILD.gn b/components/quirks/BUILD.gn
new file mode 100644
index 0000000..85c7a87
--- /dev/null
+++ b/components/quirks/BUILD.gn
@@ -0,0 +1,27 @@
+# Copyright 2016 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.
+
+source_set("quirks") {
+ sources = [
+ "pref_names.cc",
+ "pref_names.h",
+ "quirks_client.cc",
+ "quirks_client.h",
+ "quirks_export.h",
+ "quirks_manager.cc",
+ "quirks_manager.h",
+ "switches.cc",
+ "switches.h",
+ ]
+
+ defines = [ "QUIRKS_IMPLEMENTATION" ]
+
+ deps = [
+ "//base",
+ "//components/prefs",
+ "//components/version_info",
+ "//net",
+ "//url",
+ ]
+}
diff --git a/components/quirks/DEPS b/components/quirks/DEPS
new file mode 100644
index 0000000..d486509
--- /dev/null
+++ b/components/quirks/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+components/prefs",
+ "+components/version_info",
+ "+net",
+]
diff --git a/components/quirks/OWNERS b/components/quirks/OWNERS
new file mode 100644
index 0000000..469ca5f
--- /dev/null
+++ b/components/quirks/OWNERS
@@ -0,0 +1,2 @@
+glevin@chromium.org
+oshima@chromium.org
diff --git a/components/quirks/pref_names.cc b/components/quirks/pref_names.cc
new file mode 100644
index 0000000..e48eb4b
--- /dev/null
+++ b/components/quirks/pref_names.cc
@@ -0,0 +1,15 @@
+// Copyright 2016 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/quirks/pref_names.h"
+
+namespace quirks {
+namespace prefs {
+
+// A dictionary of info for Quirks Client/Server interaction, mostly last server
+// request times, keyed to display product_id's.
+const char kQuirksClientLastServerCheck[] = "quirks_client.last_server_check";
+
+} // namespace prefs
+} // namespace quirks
diff --git a/components/quirks/pref_names.h b/components/quirks/pref_names.h
new file mode 100644
index 0000000..a73065b
--- /dev/null
+++ b/components/quirks/pref_names.h
@@ -0,0 +1,16 @@
+// Copyright 2016 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_QUIRKS_PREF_NAMES_H
+#define COMPONENTS_QUIRKS_PREF_NAMES_H
+
+namespace quirks {
+namespace prefs {
+
+extern const char kQuirksClientLastServerCheck[];
+
+} // namespace prefs
+} // namespace quirks
+
+#endif // COMPONENTS_PROXIMITY_AUTH_BLE_PREF_NAMES_H
diff --git a/components/quirks/quirks_client.cc b/components/quirks/quirks_client.cc
new file mode 100644
index 0000000..16ea594
--- /dev/null
+++ b/components/quirks/quirks_client.cc
@@ -0,0 +1,171 @@
+// Copyright 2016 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/quirks/quirks_client.h"
+
+#include "base/base64.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/strings/stringprintf.h"
+#include "base/task_runner_util.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/quirks/quirks_manager.h"
+#include "components/version_info/version_info.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace quirks {
+
+namespace {
+
+const char kQuirksUrlFormat[] =
+ "https://qa-quirksserver-pa.sandbox.googleapis.com/v2/display/%s/clients"
+ "/chromeos/M%d";
+
+const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
+ 1, // Initial errors before applying backoff
+ 10000, // 10 seconds.
+ 2, // Factor by which the waiting time will be multiplied.
+ 0, // Random fuzzing percentage.
+ 1000 * 3600 * 6, // Max wait between requests = 6 hours.
+ -1, // Don't discard entry.
+ true, // Use initial delay after first error.
+};
+
+bool WriteIccFile(const base::FilePath file_path, const std::string& data) {
+ int bytes_written = base::WriteFile(file_path, data.data(), data.length());
+ if (bytes_written == -1)
+ LOG(ERROR) << "Write failed: " << file_path.value() << ", err = " << errno;
+ else
+ VLOG(1) << bytes_written << "bytes written to: " << file_path.value();
+
+ return (bytes_written != -1);
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// QuirksClient
+
+QuirksClient::QuirksClient(int64_t product_id,
+ const RequestFinishedCallback& on_request_finished,
+ QuirksManager* manager)
+ : product_id_(product_id),
+ on_request_finished_(on_request_finished),
+ manager_(manager),
+ icc_path_(
+ manager->delegate()->GetDownloadDisplayProfileDirectory().Append(
+ IdToFileName(product_id))),
+ backoff_entry_(&kDefaultBackoffPolicy),
+ weak_ptr_factory_(this) {}
+
+QuirksClient::~QuirksClient() {}
+
+void QuirksClient::StartDownload() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // URL of icc file on Quirks Server.
+ int major_version = atoi(version_info::GetVersionNumber().c_str());
+ std::string url = base::StringPrintf(
+ kQuirksUrlFormat, IdToHexString(product_id_).c_str(), major_version);
+
+ VLOG(2) << "Preparing to download\n " << url << "\nto file "
+ << icc_path_.value();
+
+ url += "?key=";
+ url += manager_->delegate()->GetApiKey();
+
+ url_fetcher_ = manager_->CreateURLFetcher(GURL(url), this);
+ url_fetcher_->SetRequestContext(manager_->url_context_getter());
+ url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
+ net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA);
+ url_fetcher_->Start();
+}
+
+void QuirksClient::OnURLFetchComplete(const net::URLFetcher* source) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(url_fetcher_.get(), source);
+
+ const int HTTP_INTERNAL_SERVER_ERROR_LAST =
+ net::HTTP_INTERNAL_SERVER_ERROR + 99;
+ const net::URLRequestStatus status = source->GetStatus();
+ const int response_code = source->GetResponseCode();
+ const bool server_error = !status.is_success() ||
+ (response_code >= net::HTTP_INTERNAL_SERVER_ERROR &&
+ response_code <= HTTP_INTERNAL_SERVER_ERROR_LAST);
+
+ VLOG(2) << "QuirksClient::OnURLFetchComplete():"
+ << " status=" << status.status()
+ << ", response_code=" << response_code
+ << ", server_error=" << server_error;
+
+ if (response_code == net::HTTP_NOT_FOUND) {
+ VLOG(1) << IdToFileName(product_id_) << " not found on Quirks server.";
+ Shutdown(false);
+ return;
+ }
+
+ if (server_error) {
+ url_fetcher_.reset();
+ Retry();
+ return;
+ }
+
+ std::string response;
+ url_fetcher_->GetResponseAsString(&response);
+ VLOG(2) << "Quirks server response:\n" << response;
+
+ // Parse response data and write to file on file thread.
+ std::string data;
+ if (!ParseResult(response, &data)) {
+ Shutdown(false);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ manager_->blocking_pool(), FROM_HERE,
+ base::Bind(&WriteIccFile, icc_path_, data),
+ base::Bind(&QuirksClient::Shutdown, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void QuirksClient::Shutdown(bool success) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ on_request_finished_.Run(success ? icc_path_ : base::FilePath(), true);
+ manager_->ClientFinished(this);
+}
+
+void QuirksClient::Retry() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ backoff_entry_.InformOfRequest(false);
+ const base::TimeDelta delay = backoff_entry_.GetTimeUntilRelease();
+
+ VLOG(1) << "Schedule next Quirks download attempt in " << delay.InSecondsF()
+ << " seconds (retry = " << backoff_entry_.failure_count() << ").";
+ request_scheduled_.Start(FROM_HERE, delay, this,
+ &QuirksClient::StartDownload);
+}
+
+bool QuirksClient::ParseResult(const std::string& result, std::string* data) {
+ std::string data64;
+ const base::DictionaryValue* dict;
+ scoped_ptr<base::Value> json = base::JSONReader::Read(result);
+ if (!json || !json->GetAsDictionary(&dict) ||
+ !dict->GetString("icc", &data64)) {
+ VLOG(1) << "Failed to parse JSON icc data";
+ return false;
+ }
+
+ if (!base::Base64Decode(data64, data)) {
+ VLOG(1) << "Failed to decode Base64 icc data";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace quirks
diff --git a/components/quirks/quirks_client.h b/components/quirks/quirks_client.h
new file mode 100644
index 0000000..d0a71fb
--- /dev/null
+++ b/components/quirks/quirks_client.h
@@ -0,0 +1,80 @@
+// Copyright 2016 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_QUIRKS_QUIRKS_CLIENT_H_
+#define COMPONENTS_QUIRKS_QUIRKS_CLIENT_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "base/timer/timer.h"
+#include "net/base/backoff_entry.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+namespace quirks {
+
+class QuirksManager;
+
+// See declaration in quirks_manager.h.
+using RequestFinishedCallback =
+ base::Callback<void(const base::FilePath&, bool)>;
+
+// Handles downloading icc and other display data files from Quirks Server.
+class QuirksClient : public net::URLFetcherDelegate {
+ public:
+ QuirksClient(int64_t product_id,
+ const RequestFinishedCallback& on_request_finished,
+ QuirksManager* manager);
+ ~QuirksClient() override;
+
+ void StartDownload();
+
+ int64_t product_id() const { return product_id_; }
+
+ private:
+ // net::URLFetcherDelegate:
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // Send callback and tell manager to delete |this|.
+ void Shutdown(bool success);
+
+ // Schedules a retry.
+ void Retry();
+
+ // Translates json with base64-encoded data (|result|) into raw |data|.
+ bool ParseResult(const std::string& result, std::string* data);
+
+ // ID of display to request from Quirks Server.
+ const int64_t product_id_;
+
+ // Callback supplied by caller.
+ const RequestFinishedCallback on_request_finished_;
+
+ // Weak pointer owned by manager, guaranteed to outlive this client object.
+ QuirksManager* manager_;
+
+ // Full path to icc file.
+ const base::FilePath icc_path_;
+
+ // The class is expected to run on UI thread.
+ base::ThreadChecker thread_checker_;
+
+ // This fetcher is used to download icc file.
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+
+ // Pending retry.
+ base::OneShotTimer request_scheduled_;
+
+ // Controls exponential backoff of time between server checks.
+ net::BackoffEntry backoff_entry_;
+
+ // Factory for callbacks.
+ base::WeakPtrFactory<QuirksClient> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuirksClient);
+};
+
+} // namespace quirks
+
+#endif // COMPONENTS_QUIRKS_QUIRKS_CLIENT_H_
diff --git a/components/quirks/quirks_export.h b/components/quirks/quirks_export.h
new file mode 100644
index 0000000..9dd4595
--- /dev/null
+++ b/components/quirks/quirks_export.h
@@ -0,0 +1,29 @@
+// Copyright 2016 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_QUIRKS_QUIRKS_EXPORT_H_
+#define COMPONENTS_QUIRKS_QUIRKS_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(QUIRKS_IMPLEMENTATION)
+#define QUIRKS_EXPORT __declspec(dllexport)
+#else
+#define QUIRKS_EXPORT __declspec(dllimport)
+#endif // defined(QUIRKS_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(QUIRKS_IMPLEMENTATION)
+#define QUIRKS_EXPORT __attribute__((visibility("default")))
+#else
+#define QUIRKS_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define QUIRKS_EXPORT
+#endif
+
+#endif // COMPONENTS_QUIRKS_QUIRKS_EXPORT_H_
diff --git a/components/quirks/quirks_manager.cc b/components/quirks/quirks_manager.cc
new file mode 100644
index 0000000..d4c6933
--- /dev/null
+++ b/components/quirks/quirks_manager.cc
@@ -0,0 +1,257 @@
+// Copyright 2016 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/quirks/quirks_manager.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/path_service.h"
+#include "base/rand_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/task_runner_util.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/quirks/pref_names.h"
+#include "components/quirks/quirks_client.h"
+#include "components/quirks/switches.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+
+namespace quirks {
+
+namespace {
+
+QuirksManager* manager_ = nullptr;
+
+const char kIccExtension[] = ".icc";
+
+// How often we query Quirks Server.
+const int kDaysBetweenServerChecks = 30;
+
+// Check if file exists, VLOG results.
+bool CheckAndLogFile(const base::FilePath& path) {
+ const bool exists = base::PathExists(path);
+ VLOG(1) << (exists ? "File" : "No File") << " found at " << path.value();
+ // TODO(glevin): If file exists, do we want to implement a hash to verify that
+ // the file hasn't been corrupted or tampered with?
+ return exists;
+}
+
+base::FilePath CheckForIccFile(base::FilePath built_in_path,
+ base::FilePath download_path) {
+ // First, look for icc file in old read-only location. If there, we don't use
+ // the Quirks server.
+ // TODO(glevin): Awaiting final decision on how to handle old read-only files.
+ if (CheckAndLogFile(built_in_path))
+ return built_in_path;
+
+ // If experimental Quirks flag isn't set, no other icc file is available.
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableQuirksClient)) {
+ VLOG(1) << "Quirks Client disabled, no built-in icc file available.";
+ return base::FilePath();
+ }
+
+ // Check if QuirksClient has already downloaded icc file from server.
+ if (CheckAndLogFile(download_path))
+ return download_path;
+
+ return base::FilePath();
+}
+
+} // namespace
+
+std::string IdToHexString(int64_t product_id) {
+ return base::StringPrintf("%08" PRIx64, product_id);
+}
+
+std::string IdToFileName(int64_t product_id) {
+ return IdToHexString(product_id).append(kIccExtension);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// QuirksManager
+
+QuirksManager::QuirksManager(
+ scoped_ptr<Delegate> delegate,
+ scoped_refptr<base::SequencedWorkerPool> blocking_pool,
+ PrefService* local_state,
+ scoped_refptr<net::URLRequestContextGetter> url_context_getter)
+ : waiting_for_login_(true),
+ delegate_(std::move(delegate)),
+ blocking_pool_(blocking_pool),
+ local_state_(local_state),
+ url_context_getter_(url_context_getter),
+ weak_ptr_factory_(this) {}
+
+QuirksManager::~QuirksManager() {
+ clients_.clear();
+ manager_ = nullptr;
+}
+
+// static
+void QuirksManager::Initialize(
+ scoped_ptr<Delegate> delegate,
+ scoped_refptr<base::SequencedWorkerPool> blocking_pool,
+ PrefService* local_state,
+ scoped_refptr<net::URLRequestContextGetter> url_context_getter) {
+ manager_ = new QuirksManager(std::move(delegate), blocking_pool, local_state,
+ url_context_getter);
+}
+
+// static
+void QuirksManager::Shutdown() {
+ delete manager_;
+}
+
+// static
+QuirksManager* QuirksManager::Get() {
+ DCHECK(manager_);
+ return manager_;
+}
+
+// static
+void QuirksManager::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterDictionaryPref(prefs::kQuirksClientLastServerCheck);
+}
+
+// Delay downloads until after login, to ensure that device policy has been set.
+void QuirksManager::OnLoginCompleted() {
+ if (!waiting_for_login_)
+ return;
+
+ waiting_for_login_ = false;
+ for (const scoped_ptr<QuirksClient>& client : clients_)
+ client->StartDownload();
+}
+
+void QuirksManager::RequestIccProfilePath(
+ int64_t product_id,
+ const RequestFinishedCallback& on_request_finished) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::string name = IdToFileName(product_id);
+ base::PostTaskAndReplyWithResult(
+ blocking_pool_.get(), FROM_HERE,
+ base::Bind(&CheckForIccFile,
+ delegate_->GetBuiltInDisplayProfileDirectory().Append(name),
+ delegate_->GetDownloadDisplayProfileDirectory().Append(name)),
+ base::Bind(&QuirksManager::OnIccFilePathRequestCompleted,
+ weak_ptr_factory_.GetWeakPtr(), product_id,
+ on_request_finished));
+}
+
+void QuirksManager::ClientFinished(QuirksClient* client) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ SetLastServerCheck(client->product_id(), base::Time::Now());
+ auto it = std::find_if(clients_.begin(), clients_.end(),
+ [client](const scoped_ptr<QuirksClient>& c) {
+ return c.get() == client;
+ });
+ CHECK(it != clients_.end());
+ clients_.erase(it);
+}
+
+scoped_ptr<net::URLFetcher> QuirksManager::CreateURLFetcher(
+ const GURL& url,
+ net::URLFetcherDelegate* delegate) {
+ if (!fake_quirks_fetcher_creator_.is_null())
+ return fake_quirks_fetcher_creator_.Run(url, delegate);
+
+ return net::URLFetcher::Create(url, net::URLFetcher::GET, delegate);
+}
+
+void QuirksManager::OnIccFilePathRequestCompleted(
+ int64_t product_id,
+ const RequestFinishedCallback& on_request_finished,
+ base::FilePath path) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // If we found a file or client is disabled, inform requester.
+ if (!path.empty() ||
+ !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableQuirksClient)) {
+ on_request_finished.Run(path, false);
+ // TODO(glevin): If Quirks files are ever modified on the server, we'll need
+ // to modify this logic to check for updates. See crbug.com/595024.
+ return;
+ }
+
+ double last_check = 0.0;
+ local_state_->GetDictionary(prefs::kQuirksClientLastServerCheck)
+ ->GetDouble(IdToHexString(product_id), &last_check);
+
+ // If never checked server before, need to check for new device.
+ if (last_check == 0.0) {
+ delegate_->GetDaysSinceOobe(base::Bind(
+ &QuirksManager::OnDaysSinceOobeReceived, weak_ptr_factory_.GetWeakPtr(),
+ product_id, on_request_finished));
+ return;
+ }
+
+ const base::TimeDelta time_since =
+ base::Time::Now() - base::Time::FromDoubleT(last_check);
+
+ // Don't need server check if we've checked within last 30 days.
+ if (time_since < base::TimeDelta::FromDays(kDaysBetweenServerChecks)) {
+ VLOG(2) << time_since.InDays()
+ << " days since last Quirks Server check for display "
+ << IdToHexString(product_id);
+ on_request_finished.Run(base::FilePath(), false);
+ return;
+ }
+
+ CreateClient(product_id, on_request_finished);
+}
+
+void QuirksManager::OnDaysSinceOobeReceived(
+ int64_t product_id,
+ const RequestFinishedCallback& on_request_finished,
+ int days_since_oobe) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // On newer devices, we want to check server immediately (after OOBE/login).
+ if (days_since_oobe <= kDaysBetweenServerChecks) {
+ CreateClient(product_id, on_request_finished);
+ return;
+ }
+
+ // Otherwise, for the first check on an older device, we want to stagger
+ // it over 30 days, so artificially set last check accordingly.
+ // TODO(glevin): I believe that it makes sense to remove this random delay
+ // in the next Chrome release.
+ const int rand_days = base::RandInt(0, kDaysBetweenServerChecks);
+ const base::Time fake_last_check =
+ base::Time::Now() - base::TimeDelta::FromDays(rand_days);
+ SetLastServerCheck(product_id, fake_last_check);
+ VLOG(2) << "Delaying first Quirks Server check by "
+ << kDaysBetweenServerChecks - rand_days << " days.";
+
+ on_request_finished.Run(base::FilePath(), false);
+}
+
+void QuirksManager::CreateClient(
+ int64_t product_id,
+ const RequestFinishedCallback& on_request_finished) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ QuirksClient* client =
+ new QuirksClient(product_id, on_request_finished, this);
+ clients_.insert(make_scoped_ptr(client));
+ if (!waiting_for_login_)
+ client->StartDownload();
+ else
+ VLOG(2) << "Quirks Client created; waiting for login to begin download.";
+}
+
+void QuirksManager::SetLastServerCheck(int64_t product_id,
+ const base::Time& last_check) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DictionaryPrefUpdate dict(local_state_, prefs::kQuirksClientLastServerCheck);
+ dict->SetDouble(IdToHexString(product_id), last_check.ToDoubleT());
+}
+
+} // namespace quirks
diff --git a/components/quirks/quirks_manager.h b/components/quirks/quirks_manager.h
new file mode 100644
index 0000000..1edf7b8
--- /dev/null
+++ b/components/quirks/quirks_manager.h
@@ -0,0 +1,178 @@
+// Copyright 2016 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_QUIRKS_QUIRKS_MANAGER_H_
+#define COMPONENTS_QUIRKS_QUIRKS_MANAGER_H_
+
+#include <set>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/quirks/quirks_export.h"
+
+class GURL;
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class SequencedWorkerPool;
+}
+
+namespace net {
+class URLFetcher;
+class URLFetcherDelegate;
+class URLRequestContextGetter;
+}
+
+namespace quirks {
+
+class QuirksClient;
+
+// Callback when Quirks path request is complete.
+// First parameter - path found, or empty if no file.
+// Second parameter - true if file was just downloaded.
+using RequestFinishedCallback =
+ base::Callback<void(const base::FilePath&, bool)>;
+
+// Format int as hex string for filename.
+QUIRKS_EXPORT std::string IdToHexString(int64_t product_id);
+
+// Append ".icc" to hex string in filename.
+QUIRKS_EXPORT std::string IdToFileName(int64_t product_id);
+
+// Manages downloads of and requests for hardware calibration and configuration
+// files ("Quirks"). The manager presents an external Quirks API, handles
+// needed components from browser (local preferences, url context getter,
+// blocking pool, etc), and owns clients and manages their life cycles.
+class QUIRKS_EXPORT QuirksManager {
+ public:
+ // Passed function to create a URLFetcher for tests.
+ // Same parameters as URLFetcher::Create().
+ using FakeQuirksFetcherCreator =
+ base::Callback<scoped_ptr<net::URLFetcher>(const GURL&,
+ net::URLFetcherDelegate*)>;
+
+ // Callback after getting days since OOBE on blocking pool.
+ // Parameter is returned number of days.
+ using DaysSinceOobeCallback = base::Callback<void(int)>;
+
+ // Delegate class, so implementation can access browser functionality.
+ class Delegate {
+ public:
+ virtual ~Delegate() = default;
+
+ // Provides Chrome API key for quirks server.
+ virtual std::string GetApiKey() const = 0;
+
+ // Returns the read-only directory where icc files were added before the
+ // Quirks Client provided them.
+ virtual base::FilePath GetBuiltInDisplayProfileDirectory() const = 0;
+
+ // Returns the path to the writable display profile directory.
+ // This directory must already exist.
+ virtual base::FilePath GetDownloadDisplayProfileDirectory() const = 0;
+
+ // Gets days since first login, returned via callback.
+ virtual void GetDaysSinceOobe(DaysSinceOobeCallback callback) const = 0;
+
+ private:
+ DISALLOW_ASSIGN(Delegate);
+ };
+
+ static void Initialize(
+ scoped_ptr<Delegate> delegate,
+ scoped_refptr<base::SequencedWorkerPool> blocking_pool,
+ PrefService* local_state,
+ scoped_refptr<net::URLRequestContextGetter> url_context_getter);
+ static void Shutdown();
+ static QuirksManager* Get();
+
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Signal to start queued downloads after login.
+ void OnLoginCompleted();
+
+ // Entry point into manager. Finds or downloads icc file.
+ void RequestIccProfilePath(
+ int64_t product_id,
+ const RequestFinishedCallback& on_request_finished);
+
+ void ClientFinished(QuirksClient* client);
+
+ // Creates a real URLFetcher for OS, and a fake one for tests.
+ scoped_ptr<net::URLFetcher> CreateURLFetcher(
+ const GURL& url,
+ net::URLFetcherDelegate* delegate);
+
+ Delegate* delegate() { return delegate_.get(); }
+ base::SequencedWorkerPool* blocking_pool() { return blocking_pool_.get(); }
+ net::URLRequestContextGetter* url_context_getter() {
+ return url_context_getter_.get();
+ }
+
+ protected:
+ friend class QuirksBrowserTest;
+
+ void SetFakeQuirksFetcherCreatorForTests(
+ const FakeQuirksFetcherCreator& creator) {
+ fake_quirks_fetcher_creator_ = creator;
+ }
+
+ private:
+ QuirksManager(scoped_ptr<Delegate> delegate,
+ scoped_refptr<base::SequencedWorkerPool> blocking_pool,
+ PrefService* local_state,
+ scoped_refptr<net::URLRequestContextGetter> url_context_getter);
+ ~QuirksManager();
+
+ // Callback after checking for existing icc file; proceed if not found.
+ void OnIccFilePathRequestCompleted(
+ int64_t product_id,
+ const RequestFinishedCallback& on_request_finished,
+ base::FilePath path);
+
+ // Callback after checking OOBE date; launch client if appropriate.
+ void OnDaysSinceOobeReceived(
+ int64_t product_id,
+ const RequestFinishedCallback& on_request_finished,
+ int days_since_oobe);
+
+ // Create and start a client to download file.
+ void CreateClient(int64_t product_id,
+ const RequestFinishedCallback& on_request_finished);
+
+ // Records time of most recent server check.
+ void SetLastServerCheck(int64_t product_id, const base::Time& last_check);
+
+ // Set of active clients, each created to download a different Quirks file.
+ std::set<scoped_ptr<QuirksClient>> clients_;
+
+ // Don't start downloads before first session login.
+ bool waiting_for_login_;
+
+ // Ensure this class runs on a single thread.
+ base::ThreadChecker thread_checker_;
+
+ // These objects provide resources from the browser.
+ scoped_ptr<Delegate> delegate_; // Impl runs from chrome/browser.
+ scoped_refptr<base::SequencedWorkerPool> blocking_pool_;
+ PrefService* local_state_; // For local prefs.
+ scoped_refptr<net::URLRequestContextGetter> url_context_getter_;
+
+ FakeQuirksFetcherCreator fake_quirks_fetcher_creator_; // For tests.
+
+ // Factory for callbacks.
+ base::WeakPtrFactory<QuirksManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuirksManager);
+};
+
+} // namespace quirks
+
+#endif // COMPONENTS_QUIRKS_QUIRKS_MANAGER_H_
diff --git a/components/quirks/switches.cc b/components/quirks/switches.cc
new file mode 100644
index 0000000..fd61a5d
--- /dev/null
+++ b/components/quirks/switches.cc
@@ -0,0 +1,14 @@
+// Copyright 2016 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/quirks/switches.h"
+
+namespace quirks {
+namespace switches {
+
+// Enable Quirks Client for handling display calibration information.
+const char kEnableQuirksClient[] = "enable-quirks-client";
+
+} // namespace switches
+} // namespace quirks
diff --git a/components/quirks/switches.h b/components/quirks/switches.h
new file mode 100644
index 0000000..e359dcf
--- /dev/null
+++ b/components/quirks/switches.h
@@ -0,0 +1,20 @@
+// Copyright 2016 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_QUIRKS_SWITCHES_H_
+#define COMPONENTS_QUIRKS_SWITCHES_H_
+
+#include "components/quirks/quirks_export.h"
+
+namespace quirks {
+namespace switches {
+
+// All switches in alphabetical order. The switches should be documented
+// alongside the definition of their values in the .cc file.
+QUIRKS_EXPORT extern const char kEnableQuirksClient[];
+
+} // namespace switches
+} // namespace quirks
+
+#endif // COMPONENTS_QUIRKS_SWITCHES_H_
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index b4a97d2..888a261 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -73175,6 +73175,7 @@ To add a new entry, add it with any value and run test to compute valid value.
<int value="-351127770" label="enable-offline-pages-as-bookmarks"/>
<int value="-349057743" label="extensions-on-chrome-urls"/>
<int value="-345838366" label="enable-hosted-apps-in-windows"/>
+ <int value="-345324571" label="enable-quirks-client"/>
<int value="-344343842" label="disable-experimental-app-list"/>
<int value="-340622848" label="disable-javascript-harmony-shipping"/>
<int value="-340255045" label="allow-nacl-socket-api"/>