Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(549)

Side by Side Diff: chrome/browser/ui/webui/chromeos/login/reset_screen_handler.cc

Issue 491263002: UI flow rework for device reset options. Reset made available from gaia screen. Tests adapted. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: ShowAndCancelTest fixed. Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/ui/webui/chromeos/login/reset_screen_handler.h ('k') | chrome/chrome_tests.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/webui/chromeos/login/reset_screen_handler.h" 5 #include "chrome/browser/ui/webui/chromeos/login/reset_screen_handler.h"
6 6
7 #include <string> 7 #include <string>
8 8
9 #include "base/command_line.h" 9 #include "base/command_line.h"
10 #include "base/metrics/histogram.h" 10 #include "base/metrics/histogram.h"
(...skipping 27 matching lines...) Expand all
38 38
39 namespace chromeos { 39 namespace chromeos {
40 40
41 ResetScreenHandler::ResetScreenHandler() 41 ResetScreenHandler::ResetScreenHandler()
42 : BaseScreenHandler(kJsScreenPath), 42 : BaseScreenHandler(kJsScreenPath),
43 delegate_(NULL), 43 delegate_(NULL),
44 show_on_init_(false), 44 show_on_init_(false),
45 restart_required_(true), 45 restart_required_(true),
46 reboot_was_requested_(false), 46 reboot_was_requested_(false),
47 rollback_available_(false), 47 rollback_available_(false),
48 rollback_checked_(false),
48 preparing_for_rollback_(false), 49 preparing_for_rollback_(false),
49 weak_ptr_factory_(this) { 50 weak_ptr_factory_(this) {
50 } 51 }
51 52
52 ResetScreenHandler::~ResetScreenHandler() { 53 ResetScreenHandler::~ResetScreenHandler() {
53 if (delegate_) 54 if (delegate_)
54 delegate_->OnActorDestroyed(this); 55 delegate_->OnActorDestroyed(this);
55 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 56 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
56 } 57 }
57 58
58 void ResetScreenHandler::PrepareToShow() { 59 void ResetScreenHandler::PrepareToShow() {
59 } 60 }
60 61
61 void ResetScreenHandler::ShowWithParams() { 62 void ResetScreenHandler::ShowWithParams() {
62 int dialog_type; 63 int dialog_type;
63 if (reboot_was_requested_) { 64 if (restart_required_) {
64 dialog_type = rollback_available_ ? 65 dialog_type = reset::DIALOG_SHORTCUT_RESTART_REQUIRED;
65 reset::DIALOG_SHORTCUT_CONFIRMING_POWERWASH_AND_ROLLBACK :
66 reset::DIALOG_SHORTCUT_CONFIRMING_POWERWASH_ONLY;
67 } else { 66 } else {
68 dialog_type = rollback_available_ ? 67 dialog_type = reset::DIALOG_SHORTCUT_OFFERING_ROLLBACK_UNAVAILABLE;
69 reset::DIALOG_SHORTCUT_OFFERING_ROLLBACK_AVAILABLE :
70 reset::DIALOG_SHORTCUT_OFFERING_ROLLBACK_UNAVAILABLE;
71 } 68 }
72 UMA_HISTOGRAM_ENUMERATION("Reset.ChromeOS.PowerwashDialogShown", 69 UMA_HISTOGRAM_ENUMERATION("Reset.ChromeOS.PowerwashDialogShown",
73 dialog_type, 70 dialog_type,
74 reset::DIALOG_VIEW_TYPE_SIZE); 71 reset::DIALOG_VIEW_TYPE_SIZE);
75 72
76 base::DictionaryValue reset_screen_params;
77 reset_screen_params.SetBoolean("showRestartMsg", restart_required_);
78 reset_screen_params.SetBoolean(
79 "showRollbackOption", rollback_available_ && !reboot_was_requested_);
80 reset_screen_params.SetBoolean(
81 "simpleConfirm", reboot_was_requested_ && !rollback_available_);
82 reset_screen_params.SetBoolean(
83 "rollbackConfirm", reboot_was_requested_ && rollback_available_);
84
85 PrefService* prefs = g_browser_process->local_state(); 73 PrefService* prefs = g_browser_process->local_state();
86 prefs->SetBoolean(prefs::kFactoryResetRequested, false); 74 prefs->SetBoolean(prefs::kFactoryResetRequested, false);
87 prefs->SetBoolean(prefs::kRollbackRequested, false);
88 prefs->CommitPendingWrite(); 75 prefs->CommitPendingWrite();
76 base::DictionaryValue reset_screen_params;
77 reset_screen_params.SetBoolean("restartRequired", restart_required_);
78 reset_screen_params.SetBoolean("rollbackAvailable", rollback_available_);
89 ShowScreen(kResetScreen, &reset_screen_params); 79 ShowScreen(kResetScreen, &reset_screen_params);
90 } 80 }
91 81
92 void ResetScreenHandler::Show() { 82 void ResetScreenHandler::Show() {
93 if (!page_is_ready()) { 83 if (!page_is_ready()) {
94 show_on_init_ = true; 84 show_on_init_ = true;
95 return; 85 return;
96 } 86 }
97 87
98 ChooseAndApplyShowScenario(); 88 ChooseAndApplyShowScenario();
99 } 89 }
100 90
101 void ResetScreenHandler::ChooseAndApplyShowScenario() { 91 void ResetScreenHandler::ChooseAndApplyShowScenario() {
102 PrefService* prefs = g_browser_process->local_state(); 92 PrefService* prefs = g_browser_process->local_state();
103 restart_required_ = !CommandLine::ForCurrentProcess()->HasSwitch( 93 restart_required_ = !CommandLine::ForCurrentProcess()->HasSwitch(
104 switches::kFirstExecAfterBoot); 94 switches::kFirstExecAfterBoot);
95
105 reboot_was_requested_ = false; 96 reboot_was_requested_ = false;
106 rollback_available_ = false; 97 rollback_available_ = false;
107 preparing_for_rollback_ = false; 98 preparing_for_rollback_ = false;
108 if (!restart_required_) // First exec after boot. 99 if (!restart_required_) // First exec after boot.
109 reboot_was_requested_ = prefs->GetBoolean(prefs::kFactoryResetRequested); 100 reboot_was_requested_ = prefs->GetBoolean(prefs::kFactoryResetRequested);
110 101
111 if (!CommandLine::ForCurrentProcess()->HasSwitch( 102 if (!CommandLine::ForCurrentProcess()->HasSwitch(
112 switches::kEnableRollbackOption)) { 103 switches::kEnableRollbackOption)) {
113 rollback_available_ = false; 104 rollback_available_ = false;
114 ShowWithParams(); 105 ShowWithParams();
115 } else if (!restart_required_ && reboot_was_requested_) { 106 } else if (!restart_required_ && reboot_was_requested_) {
116 // First exec after boot. 107 // First exec after boot.
117 PrefService* prefs = g_browser_process->local_state();
118 rollback_available_ = prefs->GetBoolean(prefs::kRollbackRequested);
119 ShowWithParams();
120 } else {
121 chromeos::DBusThreadManager::Get()->GetUpdateEngineClient()-> 108 chromeos::DBusThreadManager::Get()->GetUpdateEngineClient()->
122 CanRollbackCheck(base::Bind(&ResetScreenHandler::OnRollbackCheck, 109 CanRollbackCheck(base::Bind(&ResetScreenHandler::OnRollbackCheck,
123 weak_ptr_factory_.GetWeakPtr())); 110 weak_ptr_factory_.GetWeakPtr()));
111 } else {
112 // Will require restart.
113 ShowWithParams();
124 } 114 }
125 } 115 }
126 116
127 void ResetScreenHandler::Hide() { 117 void ResetScreenHandler::Hide() {
128 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 118 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
129 } 119 }
130 120
131 void ResetScreenHandler::SetDelegate(Delegate* delegate) { 121 void ResetScreenHandler::SetDelegate(Delegate* delegate) {
132 delegate_ = delegate; 122 delegate_ = delegate;
133 if (page_is_ready()) 123 if (page_is_ready())
134 Initialize(); 124 Initialize();
135 } 125 }
136 126
137 void ResetScreenHandler::DeclareLocalizedValues( 127 void ResetScreenHandler::DeclareLocalizedValues(
138 LocalizedValuesBuilder* builder) { 128 LocalizedValuesBuilder* builder) {
139 builder->Add("resetScreenTitle", IDS_RESET_SCREEN_TITLE); 129 builder->Add("resetScreenTitle", IDS_RESET_SCREEN_TITLE);
140 builder->Add("resetScreenAccessibleTitle", IDS_RESET_SCREEN_TITLE); 130 builder->Add("resetScreenAccessibleTitle", IDS_RESET_SCREEN_TITLE);
141 builder->Add("resetScreenIconTitle", IDS_RESET_SCREEN_ICON_TITLE); 131 builder->Add("resetScreenIconTitle", IDS_RESET_SCREEN_ICON_TITLE);
142 builder->Add("cancelButton", IDS_CANCEL); 132 builder->Add("cancelButton", IDS_CANCEL);
133 builder->Add("resetButtonReset", IDS_RESET_SCREEN_RESET);
134 builder->Add("resetButtonRestart", IDS_RELAUNCH_BUTTON);
135 builder->Add("resetButtonPowerwash", IDS_RESET_SCREEN_POWERWASH);
136 builder->Add("resetButtonPowerwashAndRollback",
137 IDS_RESET_SCREEN_POWERWASH_AND_REVERT);
143 138
144 builder->Add("resetWarningDataDetails", 139 builder->Add("resetWarningDataDetails",
145 IDS_RESET_SCREEN_WARNING_DETAILS_DATA); 140 IDS_RESET_SCREEN_WARNING_DETAILS_DATA);
146 builder->Add("resetRestartMessage", IDS_RESET_SCREEN_RESTART_MSG); 141 builder->Add("resetRestartMessage", IDS_RESET_SCREEN_RESTART_MSG);
147 builder->AddF("resetRollbackOption",
148 IDS_RESET_SCREEN_ROLLBACK_OPTION,
149 IDS_SHORT_PRODUCT_NAME);
150 builder->AddF("resetRevertPromise", 142 builder->AddF("resetRevertPromise",
151 IDS_RESET_SCREEN_PREPARING_REVERT_PROMISE, 143 IDS_RESET_SCREEN_PREPARING_REVERT_PROMISE,
152 IDS_SHORT_PRODUCT_NAME); 144 IDS_SHORT_PRODUCT_NAME);
153 builder->AddF("resetRevertSpinnerMessage", 145 builder->AddF("resetRevertSpinnerMessage",
154 IDS_RESET_SCREEN_PREPARING_REVERT_SPINNER_MESSAGE, 146 IDS_RESET_SCREEN_PREPARING_REVERT_SPINNER_MESSAGE,
155 IDS_SHORT_PRODUCT_NAME); 147 IDS_SHORT_PRODUCT_NAME);
156 148
157 // Different variants of the same UI elements for all dialog cases. 149 // Variants for screen title.
158 builder->Add("resetButtonReset", IDS_RESET_SCREEN_RESET); 150 builder->AddF("resetWarningTitle",
159 builder->Add("resetButtonRelaunch", IDS_RELAUNCH_BUTTON);
160 builder->Add("resetButtonPowerwash", IDS_RESET_SCREEN_POWERWASH);
161
162 builder->AddF(
163 "resetAndRollbackWarningTextConfirmational",
164 IDS_RESET_SCREEN_CONFIRMATION_WARNING_POWERWASH_AND_ROLLBACK_MSG,
165 IDS_SHORT_PRODUCT_NAME);
166 builder->AddF("resetWarningTextConfirmational",
167 IDS_RESET_SCREEN_CONFIRMATION_WARNING_POWERWASH_MSG,
168 IDS_SHORT_PRODUCT_NAME);
169 builder->AddF("resetWarningTextInitial",
170 IDS_RESET_SCREEN_WARNING_MSG, 151 IDS_RESET_SCREEN_WARNING_MSG,
171 IDS_SHORT_PRODUCT_NAME); 152 IDS_SHORT_PRODUCT_NAME);
153 builder->AddF("resetPowerwashWarningTitle",
154 IDS_RESET_SCREEN_WARNING_POWERWASH_MSG,
155 IDS_SHORT_PRODUCT_NAME);
156 builder->AddF("resetPowerwasAndRollbackhWarningTitle",
157 IDS_RESET_SCREEN_WARNING_POWERWASH_AND_ROLLBACK_MSG,
158 IDS_SHORT_PRODUCT_NAME);
172 159
173 builder->AddF( 160 // Variants for screen message.
174 "resetAndRollbackWarningDetailsConfirmational", 161 builder->AddF("resetPowerwashWarningDetails",
175 IDS_RESET_SCREEN_CONFIRMATION_WARNING_ROLLBACK_DETAILS, 162 IDS_RESET_SCREEN_WARNING_DETAILS,
176 IDS_SHORT_PRODUCT_NAME); 163 IDS_SHORT_PRODUCT_NAME);
177 builder->AddF("resetWarningDetailsConfirmational", 164 builder->AddF("resetPowerwashRollbackWarningDetails",
165 IDS_RESET_SCREEN_WARNING_POWERWASH_AND_ROLLBACK_MSG,
166 IDS_SHORT_PRODUCT_NAME);
167 builder->AddF("resetPowerwashConfirmationDetails",
178 IDS_RESET_SCREEN_CONFIRMATION_WARNING_DETAILS, 168 IDS_RESET_SCREEN_CONFIRMATION_WARNING_DETAILS,
179 IDS_SHORT_PRODUCT_NAME); 169 IDS_SHORT_PRODUCT_NAME);
180 builder->AddF("resetWarningDetailsInitial", 170 builder->AddF("resetPowerwashRollbackConfirmationDetails",
181 IDS_RESET_SCREEN_WARNING_DETAILS, 171 IDS_RESET_SCREEN_CONFIRMATION_WARNING_ROLLBACK_DETAILS,
182 IDS_SHORT_PRODUCT_NAME); 172 IDS_SHORT_PRODUCT_NAME);
183 } 173 }
184 174
185 // Invoked from call to CanRollbackCheck upon completion of the DBus call. 175 // Invoked from call to CanRollbackCheck upon completion of the DBus call.
186 void ResetScreenHandler::OnRollbackCheck(bool can_rollback) { 176 void ResetScreenHandler::OnRollbackCheck(bool can_rollback) {
187 VLOG(1) << "Callback from CanRollbackCheck, result " << can_rollback; 177 VLOG(1) << "Callback from CanRollbackCheck, result " << can_rollback;
188 rollback_available_ = can_rollback; 178 rollback_available_ = can_rollback;
189 ShowWithParams(); 179 ShowWithParams();
190 } 180 }
191 181
192 // static 182 // static
193 void ResetScreenHandler::RegisterPrefs(PrefRegistrySimple* registry) { 183 void ResetScreenHandler::RegisterPrefs(PrefRegistrySimple* registry) {
194 registry->RegisterBooleanPref(prefs::kFactoryResetRequested, false); 184 registry->RegisterBooleanPref(prefs::kFactoryResetRequested, false);
195 registry->RegisterBooleanPref(prefs::kRollbackRequested, false);
196 } 185 }
197 186
198 void ResetScreenHandler::Initialize() { 187 void ResetScreenHandler::Initialize() {
199 if (!page_is_ready() || !delegate_) 188 if (!page_is_ready() || !delegate_)
200 return; 189 return;
201 190
202 if (show_on_init_) { 191 if (show_on_init_) {
203 Show(); 192 Show();
204 show_on_init_ = false; 193 show_on_init_ = false;
205 } 194 }
206 } 195 }
207 196
208 void ResetScreenHandler::RegisterMessages() { 197 void ResetScreenHandler::RegisterMessages() {
209 AddCallback("cancelOnReset", &ResetScreenHandler::HandleOnCancel); 198 AddCallback("cancelOnReset", &ResetScreenHandler::HandleOnCancel);
210 AddCallback("restartOnReset", &ResetScreenHandler::HandleOnRestart); 199 AddCallback("restartOnReset", &ResetScreenHandler::HandleOnRestart);
211 AddCallback("powerwashOnReset", &ResetScreenHandler::HandleOnPowerwash); 200 AddCallback("powerwashOnReset", &ResetScreenHandler::HandleOnPowerwash);
212 AddCallback("resetOnLearnMore", &ResetScreenHandler::HandleOnLearnMore); 201 AddCallback("resetOnLearnMore", &ResetScreenHandler::HandleOnLearnMore);
202 AddCallback(
203 "showRollbackOnResetScreen", &ResetScreenHandler::HandleOnShowRollback);
204 AddCallback(
205 "hideRollbackOnResetScreen", &ResetScreenHandler::HandleOnHideRollback);
206 AddCallback(
207 "showConfirmationOnReset", &ResetScreenHandler::HandleOnShowConfirm);
213 } 208 }
214 209
215 void ResetScreenHandler::HandleOnCancel() { 210 void ResetScreenHandler::HandleOnCancel() {
216 if (preparing_for_rollback_) 211 if (preparing_for_rollback_)
217 return; 212 return;
218 if (delegate_) 213 if (delegate_)
219 delegate_->OnExit(); 214 delegate_->OnExit();
220 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); 215 DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
221 } 216 }
222 217
223 void ResetScreenHandler::HandleOnRestart(bool should_rollback) { 218 void ResetScreenHandler::HandleOnRestart() {
224 PrefService* prefs = g_browser_process->local_state(); 219 PrefService* prefs = g_browser_process->local_state();
225 prefs->SetBoolean(prefs::kFactoryResetRequested, true); 220 prefs->SetBoolean(prefs::kFactoryResetRequested, true);
226 prefs->SetBoolean(prefs::kRollbackRequested, should_rollback);
227 prefs->CommitPendingWrite(); 221 prefs->CommitPendingWrite();
228 222
229 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart(); 223 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart();
230 } 224 }
231 225
232 void ResetScreenHandler::HandleOnPowerwash(bool rollback_checked) { 226 void ResetScreenHandler::HandleOnPowerwash(bool rollback_checked) {
233 if (rollback_available_ && (rollback_checked || reboot_was_requested_)) { 227 if (rollback_available_ && rollback_checked) {
234 preparing_for_rollback_ = true; 228 preparing_for_rollback_ = true;
235 CallJS("updateViewOnRollbackCall"); 229 CallJS("updateViewOnRollbackCall");
236 DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this); 230 DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this);
231 VLOG(1) << "Starting Rollback";
237 chromeos::DBusThreadManager::Get()->GetUpdateEngineClient()->Rollback(); 232 chromeos::DBusThreadManager::Get()->GetUpdateEngineClient()->Rollback();
238 } else { 233 } else {
239 if (rollback_checked && !rollback_available_) { 234 if (rollback_checked && !rollback_available_) {
240 NOTREACHED() << 235 NOTREACHED() <<
241 "Rollback was checked but not available. Starting powerwash."; 236 "Rollback was checked but not available. Starting powerwash.";
242 } 237 }
238 VLOG(1) << "Starting Powerwash";
243 chromeos::DBusThreadManager::Get()->GetSessionManagerClient()-> 239 chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->
244 StartDeviceWipe(); 240 StartDeviceWipe();
245 } 241 }
246 } 242 }
247 243
248 void ResetScreenHandler::HandleOnLearnMore() { 244 void ResetScreenHandler::HandleOnLearnMore() {
249 if (!help_app_.get()) 245 if (!help_app_.get())
250 help_app_ = new HelpAppLauncher(GetNativeWindow()); 246 help_app_ = new HelpAppLauncher(GetNativeWindow());
251 help_app_->ShowHelpTopic(HelpAppLauncher::HELP_POWERWASH); 247 help_app_->ShowHelpTopic(HelpAppLauncher::HELP_POWERWASH);
252 } 248 }
253 249
250 void ResetScreenHandler::HandleOnShowRollback() {
251 VLOG(1) << "Requested rollback availability" << rollback_available_;
252 if (rollback_available_) {
253 UMA_HISTOGRAM_ENUMERATION(
254 "Reset.ChromeOS.PowerwashDialogShown",
255 reset::DIALOG_SHORTCUT_OFFERING_ROLLBACK_AVAILABLE,
256 reset::DIALOG_VIEW_TYPE_SIZE);
257 CallJS("showRollbackOption");
258 rollback_checked_ = true;
259 }
260 }
261
262 void ResetScreenHandler::HandleOnHideRollback() {
263 if (rollback_available_ && rollback_checked_) {
264 CallJS("hideRollbackOption");
265 rollback_checked_ = false;
266 }
267 }
268
269 void ResetScreenHandler::HandleOnShowConfirm() {
270 int dialog_type = rollback_checked_ ?
271 reset::DIALOG_SHORTCUT_CONFIRMING_POWERWASH_AND_ROLLBACK :
272 reset::DIALOG_SHORTCUT_CONFIRMING_POWERWASH_ONLY;
273 UMA_HISTOGRAM_ENUMERATION(
274 "Reset.ChromeOS.PowerwashDialogShown",
275 dialog_type,
276 reset::DIALOG_VIEW_TYPE_SIZE);
277 }
278
254 void ResetScreenHandler::UpdateStatusChanged( 279 void ResetScreenHandler::UpdateStatusChanged(
255 const UpdateEngineClient::Status& status) { 280 const UpdateEngineClient::Status& status) {
256 VLOG(1) << "Update status change to " << status.status; 281 VLOG(1) << "Update status change to " << status.status;
257 if (status.status == UpdateEngineClient::UPDATE_STATUS_ERROR || 282 if (status.status == UpdateEngineClient::UPDATE_STATUS_ERROR ||
258 status.status == 283 status.status ==
259 UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT) { 284 UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT) {
260 preparing_for_rollback_ = false; 285 preparing_for_rollback_ = false;
261 // Show error screen. 286 // Show error screen.
262 base::DictionaryValue params; 287 base::DictionaryValue params;
263 params.SetInteger("uiState", kErrorUIStateRollback); 288 params.SetInteger("uiState", kErrorUIStateRollback);
264 ShowScreen(OobeUI::kScreenErrorMessage, &params); 289 ShowScreen(OobeUI::kScreenErrorMessage, &params);
265 } else if (status.status == 290 } else if (status.status ==
266 UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT) { 291 UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT) {
267 DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart(); 292 DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart();
268 } 293 }
269 } 294 }
270 295
271 } // namespace chromeos 296 } // namespace chromeos
OLDNEW
« no previous file with comments | « chrome/browser/ui/webui/chromeos/login/reset_screen_handler.h ('k') | chrome/chrome_tests.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698