Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / themes / theme_syncable_service.cc
blobcc169a755a601d66788de444dae3caa16ab577bb
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/themes/theme_syncable_service.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/version.h"
9 #include "chrome/browser/extensions/extension_service.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/themes/theme_service.h"
12 #include "chrome/common/extensions/sync_helper.h"
13 #include "extensions/browser/extension_prefs.h"
14 #include "extensions/browser/extension_system.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/manifest_url_handlers.h"
17 #include "sync/protocol/sync.pb.h"
18 #include "sync/protocol/theme_specifics.pb.h"
20 using std::string;
22 namespace {
24 bool IsTheme(const extensions::Extension* extension) {
25 return extension->is_theme();
28 } // namespace
30 const char ThemeSyncableService::kCurrentThemeClientTag[] = "current_theme";
31 const char ThemeSyncableService::kCurrentThemeNodeTitle[] = "Current Theme";
33 ThemeSyncableService::ThemeSyncableService(Profile* profile,
34 ThemeService* theme_service)
35 : profile_(profile),
36 theme_service_(theme_service),
37 use_system_theme_by_default_(false) {
38 DCHECK(profile_);
39 DCHECK(theme_service_);
42 ThemeSyncableService::~ThemeSyncableService() {
45 void ThemeSyncableService::OnThemeChange() {
46 if (sync_processor_.get()) {
47 sync_pb::ThemeSpecifics current_specifics;
48 if (!GetThemeSpecificsFromCurrentTheme(&current_specifics))
49 return; // Current theme is unsyncable.
50 ProcessNewTheme(syncer::SyncChange::ACTION_UPDATE, current_specifics);
51 use_system_theme_by_default_ =
52 current_specifics.use_system_theme_by_default();
56 syncer::SyncMergeResult ThemeSyncableService::MergeDataAndStartSyncing(
57 syncer::ModelType type,
58 const syncer::SyncDataList& initial_sync_data,
59 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
60 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
61 DCHECK(thread_checker_.CalledOnValidThread());
62 DCHECK(!sync_processor_.get());
63 DCHECK(sync_processor.get());
64 DCHECK(error_handler.get());
66 syncer::SyncMergeResult merge_result(type);
67 sync_processor_ = sync_processor.Pass();
68 sync_error_handler_ = error_handler.Pass();
70 if (initial_sync_data.size() > 1) {
71 sync_error_handler_->CreateAndUploadError(
72 FROM_HERE,
73 base::StringPrintf("Received %d theme specifics.",
74 static_cast<int>(initial_sync_data.size())));
77 sync_pb::ThemeSpecifics current_specifics;
78 if (!GetThemeSpecificsFromCurrentTheme(&current_specifics)) {
79 // Current theme is unsyncable - don't overwrite from sync data, and don't
80 // save the unsyncable theme to sync data.
81 return merge_result;
84 // Find the last SyncData that has theme data and set the current theme from
85 // it.
86 for (syncer::SyncDataList::const_reverse_iterator sync_data =
87 initial_sync_data.rbegin(); sync_data != initial_sync_data.rend();
88 ++sync_data) {
89 if (sync_data->GetSpecifics().has_theme()) {
90 if (!current_specifics.use_custom_theme() ||
91 sync_data->GetSpecifics().theme().use_custom_theme()) {
92 MaybeSetTheme(current_specifics, *sync_data);
93 return merge_result;
98 // No theme specifics are found. Create one according to current theme.
99 merge_result.set_error(ProcessNewTheme(
100 syncer::SyncChange::ACTION_ADD, current_specifics));
101 return merge_result;
104 void ThemeSyncableService::StopSyncing(syncer::ModelType type) {
105 DCHECK(thread_checker_.CalledOnValidThread());
106 DCHECK_EQ(type, syncer::THEMES);
108 sync_processor_.reset();
109 sync_error_handler_.reset();
112 syncer::SyncDataList ThemeSyncableService::GetAllSyncData(
113 syncer::ModelType type) const {
114 DCHECK(thread_checker_.CalledOnValidThread());
115 DCHECK_EQ(type, syncer::THEMES);
117 syncer::SyncDataList list;
118 sync_pb::EntitySpecifics entity_specifics;
119 if (GetThemeSpecificsFromCurrentTheme(entity_specifics.mutable_theme())) {
120 list.push_back(syncer::SyncData::CreateLocalData(kCurrentThemeClientTag,
121 kCurrentThemeNodeTitle,
122 entity_specifics));
124 return list;
127 syncer::SyncError ThemeSyncableService::ProcessSyncChanges(
128 const tracked_objects::Location& from_here,
129 const syncer::SyncChangeList& change_list) {
130 DCHECK(thread_checker_.CalledOnValidThread());
132 if (!sync_processor_.get()) {
133 return syncer::SyncError(FROM_HERE,
134 syncer::SyncError::DATATYPE_ERROR,
135 "Theme syncable service is not started.",
136 syncer::THEMES);
139 // TODO(akalin): Normally, we should only have a single change and
140 // it should be an update. However, the syncapi may occasionally
141 // generates multiple changes. When we fix syncapi to not do that,
142 // we can remove the extra logic below. See:
143 // http://code.google.com/p/chromium/issues/detail?id=41696 .
144 if (change_list.size() != 1) {
145 string err_msg = base::StringPrintf("Received %d theme changes: ",
146 static_cast<int>(change_list.size()));
147 for (size_t i = 0; i < change_list.size(); ++i) {
148 base::StringAppendF(&err_msg, "[%s] ", change_list[i].ToString().c_str());
150 sync_error_handler_->CreateAndUploadError(FROM_HERE, err_msg);
151 } else if (change_list.begin()->change_type() !=
152 syncer::SyncChange::ACTION_ADD
153 && change_list.begin()->change_type() !=
154 syncer::SyncChange::ACTION_UPDATE) {
155 sync_error_handler_->CreateAndUploadError(
156 FROM_HERE,
157 "Invalid theme change: " + change_list.begin()->ToString());
160 sync_pb::ThemeSpecifics current_specifics;
161 if (!GetThemeSpecificsFromCurrentTheme(&current_specifics)) {
162 // Current theme is unsyncable, so don't overwrite it.
163 return syncer::SyncError();
166 // Set current theme from the theme specifics of the last change of type
167 // |ACTION_ADD| or |ACTION_UPDATE|.
168 for (syncer::SyncChangeList::const_reverse_iterator theme_change =
169 change_list.rbegin(); theme_change != change_list.rend();
170 ++theme_change) {
171 if (theme_change->sync_data().GetSpecifics().has_theme() &&
172 (theme_change->change_type() == syncer::SyncChange::ACTION_ADD ||
173 theme_change->change_type() == syncer::SyncChange::ACTION_UPDATE)) {
174 MaybeSetTheme(current_specifics, theme_change->sync_data());
175 return syncer::SyncError();
179 return syncer::SyncError(FROM_HERE,
180 syncer::SyncError::DATATYPE_ERROR,
181 "Didn't find valid theme specifics",
182 syncer::THEMES);
185 void ThemeSyncableService::MaybeSetTheme(
186 const sync_pb::ThemeSpecifics& current_specs,
187 const syncer::SyncData& sync_data) {
188 const sync_pb::ThemeSpecifics& sync_theme = sync_data.GetSpecifics().theme();
189 use_system_theme_by_default_ = sync_theme.use_system_theme_by_default();
190 DVLOG(1) << "Set current theme from specifics: " << sync_data.ToString();
191 if (!AreThemeSpecificsEqual(
192 current_specs,
193 sync_theme,
194 theme_service_->IsSystemThemeDistinctFromDefaultTheme())) {
195 SetCurrentThemeFromThemeSpecifics(sync_theme);
196 } else {
197 DVLOG(1) << "Skip setting theme because specs are equal";
201 void ThemeSyncableService::SetCurrentThemeFromThemeSpecifics(
202 const sync_pb::ThemeSpecifics& theme_specifics) {
203 if (theme_specifics.use_custom_theme()) {
204 // TODO(akalin): Figure out what to do about third-party themes
205 // (i.e., those not on either Google gallery).
206 string id(theme_specifics.custom_theme_id());
207 GURL update_url(theme_specifics.custom_theme_update_url());
208 DVLOG(1) << "Applying theme " << id << " with update_url " << update_url;
209 ExtensionService* extensions_service =
210 extensions::ExtensionSystem::Get(profile_)->extension_service();
211 CHECK(extensions_service);
212 const extensions::Extension* extension =
213 extensions_service->GetExtensionById(id, true);
214 if (extension) {
215 if (!extension->is_theme()) {
216 DVLOG(1) << "Extension " << id << " is not a theme; aborting";
217 return;
219 int disabled_reasons =
220 extensions::ExtensionPrefs::Get(profile_)->GetDisableReasons(id);
221 if (!extensions_service->IsExtensionEnabled(id) &&
222 disabled_reasons != extensions::Extension::DISABLE_USER_ACTION) {
223 DVLOG(1) << "Theme " << id << " is disabled with reason "
224 << disabled_reasons << "; aborting";
225 return;
227 // An enabled theme extension with the given id was found, so
228 // just set the current theme to it.
229 theme_service_->SetTheme(extension);
230 } else {
231 // No extension with this id exists -- we must install it; we do
232 // so by adding it as a pending extension and then triggering an
233 // auto-update cycle.
234 const bool kRemoteInstall = false;
235 const bool kInstalledByCustodian = false;
236 if (!extensions_service->pending_extension_manager()->AddFromSync(
238 update_url,
239 base::Version(),
240 &IsTheme,
241 kRemoteInstall,
242 kInstalledByCustodian)) {
243 LOG(WARNING) << "Could not add pending extension for " << id;
244 return;
246 extensions_service->CheckForUpdatesSoon();
248 } else if (theme_specifics.use_system_theme_by_default()) {
249 DVLOG(1) << "Switch to use system theme";
250 theme_service_->UseSystemTheme();
251 } else {
252 DVLOG(1) << "Switch to use default theme";
253 theme_service_->UseDefaultTheme();
257 bool ThemeSyncableService::GetThemeSpecificsFromCurrentTheme(
258 sync_pb::ThemeSpecifics* theme_specifics) const {
259 const extensions::Extension* current_theme =
260 theme_service_->UsingDefaultTheme() ?
261 NULL :
262 extensions::ExtensionSystem::Get(profile_)->extension_service()->
263 GetExtensionById(theme_service_->GetThemeID(), false);
264 if (current_theme && !extensions::sync_helper::IsSyncable(current_theme)) {
265 DVLOG(1) << "Ignoring non-syncable extension: " << current_theme->id();
266 return false;
268 bool use_custom_theme = (current_theme != NULL);
269 theme_specifics->set_use_custom_theme(use_custom_theme);
270 if (theme_service_->IsSystemThemeDistinctFromDefaultTheme()) {
271 // On platform where system theme is different from default theme, set
272 // use_system_theme_by_default to true if system theme is used, false
273 // if default system theme is used. Otherwise restore it to value in sync.
274 if (theme_service_->UsingSystemTheme()) {
275 theme_specifics->set_use_system_theme_by_default(true);
276 } else if (theme_service_->UsingDefaultTheme()) {
277 theme_specifics->set_use_system_theme_by_default(false);
278 } else {
279 theme_specifics->set_use_system_theme_by_default(
280 use_system_theme_by_default_);
282 } else {
283 // Restore use_system_theme_by_default when platform doesn't distinguish
284 // between default theme and system theme.
285 theme_specifics->set_use_system_theme_by_default(
286 use_system_theme_by_default_);
289 if (use_custom_theme) {
290 DCHECK(current_theme);
291 DCHECK(current_theme->is_theme());
292 theme_specifics->set_custom_theme_name(current_theme->name());
293 theme_specifics->set_custom_theme_id(current_theme->id());
294 theme_specifics->set_custom_theme_update_url(
295 extensions::ManifestURL::GetUpdateURL(current_theme).spec());
296 } else {
297 DCHECK(!current_theme);
298 theme_specifics->clear_custom_theme_name();
299 theme_specifics->clear_custom_theme_id();
300 theme_specifics->clear_custom_theme_update_url();
302 return true;
305 /* static */
306 bool ThemeSyncableService::AreThemeSpecificsEqual(
307 const sync_pb::ThemeSpecifics& a,
308 const sync_pb::ThemeSpecifics& b,
309 bool is_system_theme_distinct_from_default_theme) {
310 if (a.use_custom_theme() != b.use_custom_theme()) {
311 return false;
314 if (a.use_custom_theme()) {
315 // We're using a custom theme, so simply compare IDs since those
316 // are guaranteed unique.
317 return a.custom_theme_id() == b.custom_theme_id();
318 } else if (is_system_theme_distinct_from_default_theme) {
319 // We're not using a custom theme, but we care about system
320 // vs. default.
321 return a.use_system_theme_by_default() == b.use_system_theme_by_default();
322 } else {
323 // We're not using a custom theme, and we don't care about system
324 // vs. default.
325 return true;
329 syncer::SyncError ThemeSyncableService::ProcessNewTheme(
330 syncer::SyncChange::SyncChangeType change_type,
331 const sync_pb::ThemeSpecifics& theme_specifics) {
332 syncer::SyncChangeList changes;
333 sync_pb::EntitySpecifics entity_specifics;
334 entity_specifics.mutable_theme()->CopyFrom(theme_specifics);
336 changes.push_back(
337 syncer::SyncChange(FROM_HERE, change_type,
338 syncer::SyncData::CreateLocalData(
339 kCurrentThemeClientTag, kCurrentThemeNodeTitle,
340 entity_specifics)));
342 DVLOG(1) << "Update theme specifics from current theme: "
343 << changes.back().ToString();
345 return sync_processor_->ProcessSyncChanges(FROM_HERE, changes);