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 "chrome/browser/extensions/extension_service.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/themes/theme_service.h"
11 #include "chrome/common/extensions/sync_helper.h"
12 #include "extensions/browser/extension_prefs.h"
13 #include "extensions/browser/extension_system.h"
14 #include "extensions/common/extension.h"
15 #include "extensions/common/manifest_url_handlers.h"
16 #include "sync/protocol/sync.pb.h"
17 #include "sync/protocol/theme_specifics.pb.h"
23 bool IsTheme(const extensions::Extension
* extension
) {
24 return extension
->is_theme();
29 const char ThemeSyncableService::kCurrentThemeClientTag
[] = "current_theme";
30 const char ThemeSyncableService::kCurrentThemeNodeTitle
[] = "Current Theme";
32 ThemeSyncableService::ThemeSyncableService(Profile
* profile
,
33 ThemeService
* theme_service
)
35 theme_service_(theme_service
),
36 use_system_theme_by_default_(false) {
38 DCHECK(theme_service_
);
41 ThemeSyncableService::~ThemeSyncableService() {
44 void ThemeSyncableService::OnThemeChange() {
45 if (sync_processor_
.get()) {
46 sync_pb::ThemeSpecifics current_specifics
;
47 if (!GetThemeSpecificsFromCurrentTheme(¤t_specifics
))
48 return; // Current theme is unsyncable.
49 ProcessNewTheme(syncer::SyncChange::ACTION_UPDATE
, current_specifics
);
50 use_system_theme_by_default_
=
51 current_specifics
.use_system_theme_by_default();
55 syncer::SyncMergeResult
ThemeSyncableService::MergeDataAndStartSyncing(
56 syncer::ModelType type
,
57 const syncer::SyncDataList
& initial_sync_data
,
58 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
59 scoped_ptr
<syncer::SyncErrorFactory
> error_handler
) {
60 DCHECK(thread_checker_
.CalledOnValidThread());
61 DCHECK(!sync_processor_
.get());
62 DCHECK(sync_processor
.get());
63 DCHECK(error_handler
.get());
65 syncer::SyncMergeResult
merge_result(type
);
66 sync_processor_
= sync_processor
.Pass();
67 sync_error_handler_
= error_handler
.Pass();
69 if (initial_sync_data
.size() > 1) {
70 sync_error_handler_
->CreateAndUploadError(
72 base::StringPrintf("Received %d theme specifics.",
73 static_cast<int>(initial_sync_data
.size())));
76 sync_pb::ThemeSpecifics current_specifics
;
77 if (!GetThemeSpecificsFromCurrentTheme(¤t_specifics
)) {
78 // Current theme is unsyncable - don't overwrite from sync data, and don't
79 // save the unsyncable theme to sync data.
83 // Find the last SyncData that has theme data and set the current theme from
85 for (syncer::SyncDataList::const_reverse_iterator sync_data
=
86 initial_sync_data
.rbegin(); sync_data
!= initial_sync_data
.rend();
88 if (sync_data
->GetSpecifics().has_theme()) {
89 if (!current_specifics
.use_custom_theme() ||
90 sync_data
->GetSpecifics().theme().use_custom_theme()) {
91 MaybeSetTheme(current_specifics
, *sync_data
);
97 // No theme specifics are found. Create one according to current theme.
98 merge_result
.set_error(ProcessNewTheme(
99 syncer::SyncChange::ACTION_ADD
, current_specifics
));
103 void ThemeSyncableService::StopSyncing(syncer::ModelType type
) {
104 DCHECK(thread_checker_
.CalledOnValidThread());
105 DCHECK_EQ(type
, syncer::THEMES
);
107 sync_processor_
.reset();
108 sync_error_handler_
.reset();
111 syncer::SyncDataList
ThemeSyncableService::GetAllSyncData(
112 syncer::ModelType type
) const {
113 DCHECK(thread_checker_
.CalledOnValidThread());
114 DCHECK_EQ(type
, syncer::THEMES
);
116 syncer::SyncDataList list
;
117 sync_pb::EntitySpecifics entity_specifics
;
118 if (GetThemeSpecificsFromCurrentTheme(entity_specifics
.mutable_theme())) {
119 list
.push_back(syncer::SyncData::CreateLocalData(kCurrentThemeClientTag
,
120 kCurrentThemeNodeTitle
,
126 syncer::SyncError
ThemeSyncableService::ProcessSyncChanges(
127 const tracked_objects::Location
& from_here
,
128 const syncer::SyncChangeList
& change_list
) {
129 DCHECK(thread_checker_
.CalledOnValidThread());
131 if (!sync_processor_
.get()) {
132 return syncer::SyncError(FROM_HERE
,
133 syncer::SyncError::DATATYPE_ERROR
,
134 "Theme syncable service is not started.",
138 // TODO(akalin): Normally, we should only have a single change and
139 // it should be an update. However, the syncapi may occasionally
140 // generates multiple changes. When we fix syncapi to not do that,
141 // we can remove the extra logic below. See:
142 // http://code.google.com/p/chromium/issues/detail?id=41696 .
143 if (change_list
.size() != 1) {
144 string err_msg
= base::StringPrintf("Received %d theme changes: ",
145 static_cast<int>(change_list
.size()));
146 for (size_t i
= 0; i
< change_list
.size(); ++i
) {
147 base::StringAppendF(&err_msg
, "[%s] ", change_list
[i
].ToString().c_str());
149 sync_error_handler_
->CreateAndUploadError(FROM_HERE
, err_msg
);
150 } else if (change_list
.begin()->change_type() !=
151 syncer::SyncChange::ACTION_ADD
152 && change_list
.begin()->change_type() !=
153 syncer::SyncChange::ACTION_UPDATE
) {
154 sync_error_handler_
->CreateAndUploadError(
156 "Invalid theme change: " + change_list
.begin()->ToString());
159 sync_pb::ThemeSpecifics current_specifics
;
160 if (!GetThemeSpecificsFromCurrentTheme(¤t_specifics
)) {
161 // Current theme is unsyncable, so don't overwrite it.
162 return syncer::SyncError();
165 // Set current theme from the theme specifics of the last change of type
166 // |ACTION_ADD| or |ACTION_UPDATE|.
167 for (syncer::SyncChangeList::const_reverse_iterator theme_change
=
168 change_list
.rbegin(); theme_change
!= change_list
.rend();
170 if (theme_change
->sync_data().GetSpecifics().has_theme() &&
171 (theme_change
->change_type() == syncer::SyncChange::ACTION_ADD
||
172 theme_change
->change_type() == syncer::SyncChange::ACTION_UPDATE
)) {
173 MaybeSetTheme(current_specifics
, theme_change
->sync_data());
174 return syncer::SyncError();
178 return syncer::SyncError(FROM_HERE
,
179 syncer::SyncError::DATATYPE_ERROR
,
180 "Didn't find valid theme specifics",
184 void ThemeSyncableService::MaybeSetTheme(
185 const sync_pb::ThemeSpecifics
& current_specs
,
186 const syncer::SyncData
& sync_data
) {
187 const sync_pb::ThemeSpecifics
& sync_theme
= sync_data
.GetSpecifics().theme();
188 use_system_theme_by_default_
= sync_theme
.use_system_theme_by_default();
189 DVLOG(1) << "Set current theme from specifics: " << sync_data
.ToString();
190 if (!AreThemeSpecificsEqual(
193 theme_service_
->IsSystemThemeDistinctFromDefaultTheme())) {
194 SetCurrentThemeFromThemeSpecifics(sync_theme
);
196 DVLOG(1) << "Skip setting theme because specs are equal";
200 void ThemeSyncableService::SetCurrentThemeFromThemeSpecifics(
201 const sync_pb::ThemeSpecifics
& theme_specifics
) {
202 if (theme_specifics
.use_custom_theme()) {
203 // TODO(akalin): Figure out what to do about third-party themes
204 // (i.e., those not on either Google gallery).
205 string
id(theme_specifics
.custom_theme_id());
206 GURL
update_url(theme_specifics
.custom_theme_update_url());
207 DVLOG(1) << "Applying theme " << id
<< " with update_url " << update_url
;
208 ExtensionService
* extensions_service
=
209 extensions::ExtensionSystem::Get(profile_
)->extension_service();
210 CHECK(extensions_service
);
211 const extensions::Extension
* extension
=
212 extensions_service
->GetExtensionById(id
, true);
214 if (!extension
->is_theme()) {
215 DVLOG(1) << "Extension " << id
<< " is not a theme; aborting";
218 int disabled_reasons
=
219 extensions::ExtensionPrefs::Get(profile_
)->GetDisableReasons(id
);
220 if (!extensions_service
->IsExtensionEnabled(id
) &&
221 disabled_reasons
!= extensions::Extension::DISABLE_USER_ACTION
) {
222 DVLOG(1) << "Theme " << id
<< " is disabled with reason "
223 << disabled_reasons
<< "; aborting";
226 // An enabled theme extension with the given id was found, so
227 // just set the current theme to it.
228 theme_service_
->SetTheme(extension
);
230 // No extension with this id exists -- we must install it; we do
231 // so by adding it as a pending extension and then triggering an
232 // auto-update cycle.
233 const bool kRemoteInstall
= false;
234 const bool kInstalledByCustodian
= false;
235 if (!extensions_service
->pending_extension_manager()->AddFromSync(
240 kInstalledByCustodian
)) {
241 LOG(WARNING
) << "Could not add pending extension for " << id
;
244 extensions_service
->CheckForUpdatesSoon();
246 } else if (theme_specifics
.use_system_theme_by_default()) {
247 DVLOG(1) << "Switch to use system theme";
248 theme_service_
->UseSystemTheme();
250 DVLOG(1) << "Switch to use default theme";
251 theme_service_
->UseDefaultTheme();
255 bool ThemeSyncableService::GetThemeSpecificsFromCurrentTheme(
256 sync_pb::ThemeSpecifics
* theme_specifics
) const {
257 const extensions::Extension
* current_theme
=
258 theme_service_
->UsingDefaultTheme() ?
260 extensions::ExtensionSystem::Get(profile_
)->extension_service()->
261 GetExtensionById(theme_service_
->GetThemeID(), false);
262 if (current_theme
&& !extensions::sync_helper::IsSyncable(current_theme
)) {
263 DVLOG(1) << "Ignoring non-syncable extension: " << current_theme
->id();
266 bool use_custom_theme
= (current_theme
!= NULL
);
267 theme_specifics
->set_use_custom_theme(use_custom_theme
);
268 if (theme_service_
->IsSystemThemeDistinctFromDefaultTheme()) {
269 // On platform where system theme is different from default theme, set
270 // use_system_theme_by_default to true if system theme is used, false
271 // if default system theme is used. Otherwise restore it to value in sync.
272 if (theme_service_
->UsingSystemTheme()) {
273 theme_specifics
->set_use_system_theme_by_default(true);
274 } else if (theme_service_
->UsingDefaultTheme()) {
275 theme_specifics
->set_use_system_theme_by_default(false);
277 theme_specifics
->set_use_system_theme_by_default(
278 use_system_theme_by_default_
);
281 // Restore use_system_theme_by_default when platform doesn't distinguish
282 // between default theme and system theme.
283 theme_specifics
->set_use_system_theme_by_default(
284 use_system_theme_by_default_
);
287 if (use_custom_theme
) {
288 DCHECK(current_theme
);
289 DCHECK(current_theme
->is_theme());
290 theme_specifics
->set_custom_theme_name(current_theme
->name());
291 theme_specifics
->set_custom_theme_id(current_theme
->id());
292 theme_specifics
->set_custom_theme_update_url(
293 extensions::ManifestURL::GetUpdateURL(current_theme
).spec());
295 DCHECK(!current_theme
);
296 theme_specifics
->clear_custom_theme_name();
297 theme_specifics
->clear_custom_theme_id();
298 theme_specifics
->clear_custom_theme_update_url();
304 bool ThemeSyncableService::AreThemeSpecificsEqual(
305 const sync_pb::ThemeSpecifics
& a
,
306 const sync_pb::ThemeSpecifics
& b
,
307 bool is_system_theme_distinct_from_default_theme
) {
308 if (a
.use_custom_theme() != b
.use_custom_theme()) {
312 if (a
.use_custom_theme()) {
313 // We're using a custom theme, so simply compare IDs since those
314 // are guaranteed unique.
315 return a
.custom_theme_id() == b
.custom_theme_id();
316 } else if (is_system_theme_distinct_from_default_theme
) {
317 // We're not using a custom theme, but we care about system
319 return a
.use_system_theme_by_default() == b
.use_system_theme_by_default();
321 // We're not using a custom theme, and we don't care about system
327 syncer::SyncError
ThemeSyncableService::ProcessNewTheme(
328 syncer::SyncChange::SyncChangeType change_type
,
329 const sync_pb::ThemeSpecifics
& theme_specifics
) {
330 syncer::SyncChangeList changes
;
331 sync_pb::EntitySpecifics entity_specifics
;
332 entity_specifics
.mutable_theme()->CopyFrom(theme_specifics
);
335 syncer::SyncChange(FROM_HERE
, change_type
,
336 syncer::SyncData::CreateLocalData(
337 kCurrentThemeClientTag
, kCurrentThemeNodeTitle
,
340 DVLOG(1) << "Update theme specifics from current theme: "
341 << changes
.back().ToString();
343 return sync_processor_
->ProcessSyncChanges(FROM_HERE
, changes
);