OLD | NEW |
| (Empty) |
1 // Copyright 2007-2010 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 | |
16 | |
17 #include "omaha/ui/progress_wnd.h" | |
18 #include "base/basictypes.h" | |
19 #include "omaha/base/constants.h" | |
20 #include "omaha/base/debug.h" | |
21 #include "omaha/base/error.h" | |
22 #include "omaha/base/logging.h" | |
23 #include "omaha/base/system_info.h" | |
24 #include "omaha/base/utils.h" | |
25 #include "omaha/common/goopdate_utils.h" | |
26 #include "omaha/ui/ui_ctls.h" | |
27 #include "omaha/ui/ui_metrics.h" | |
28 | |
29 namespace omaha { | |
30 | |
31 namespace { | |
32 | |
33 // The current UI is only able to show one completion type. If apps in the | |
34 // bundle have different completion type, then we need to decide which | |
35 // one should be shown to the user. The following array lists the types | |
36 // from low priority to high priority. The completion type with highest | |
37 // priority will be shown to the user. | |
38 const CompletionCodes kCompletionCodesActionPriority[] = { | |
39 COMPLETION_CODE_EXIT_SILENTLY, | |
40 COMPLETION_CODE_EXIT_SILENTLY_ON_LAUNCH_COMMAND, | |
41 COMPLETION_CODE_SUCCESS, | |
42 COMPLETION_CODE_LAUNCH_COMMAND, | |
43 COMPLETION_CODE_RESTART_BROWSER_NOTICE_ONLY, | |
44 COMPLETION_CODE_RESTART_ALL_BROWSERS_NOTICE_ONLY, | |
45 COMPLETION_CODE_RESTART_BROWSER, | |
46 COMPLETION_CODE_RESTART_ALL_BROWSERS, | |
47 COMPLETION_CODE_REBOOT_NOTICE_ONLY, | |
48 COMPLETION_CODE_REBOOT, | |
49 COMPLETION_CODE_ERROR, | |
50 COMPLETION_CODE_INSTALL_FINISHED_BEFORE_CANCEL, | |
51 }; | |
52 | |
53 // kCompletionCodesActionPriority should have all the values in enumeration | |
54 // CompletionCodes. The enumeration value starts from 1 so the array size | |
55 // should match the last value in the enumeration. | |
56 COMPILE_ASSERT(arraysize(kCompletionCodesActionPriority) == | |
57 COMPLETION_CODE_INSTALL_FINISHED_BEFORE_CANCEL, | |
58 CompletionCodesActionPriority_missing_completion_code); | |
59 | |
60 int GetActionPriority(CompletionCodes code) { | |
61 for (int i = 0; i < arraysize(kCompletionCodesActionPriority); ++i) { | |
62 if (kCompletionCodesActionPriority[i] == code) { | |
63 return i; | |
64 } | |
65 } | |
66 | |
67 ASSERT1(false); | |
68 return -1; | |
69 } | |
70 | |
71 bool AreAllAppsCanceled(const std::vector<AppCompletionInfo>& apps_info) { | |
72 for (size_t i = 0; i < apps_info.size(); ++i) { | |
73 if (!apps_info[i].is_canceled) { | |
74 return false; | |
75 } | |
76 } | |
77 | |
78 return true; | |
79 } | |
80 | |
81 } // namespace | |
82 | |
83 InstallStoppedWnd::InstallStoppedWnd(CMessageLoop* message_loop, HWND parent) | |
84 : message_loop_(message_loop), | |
85 parent_(parent) { | |
86 CORE_LOG(L3, (_T("[InstallStoppedWnd::InstallStoppedWnd]"))); | |
87 ASSERT1(message_loop); | |
88 ASSERT1(::IsWindow(parent)); | |
89 } | |
90 | |
91 InstallStoppedWnd::~InstallStoppedWnd() { | |
92 CORE_LOG(L3, (_T("[InstallStoppedWnd::~InstallStoppedWnd]"))); | |
93 if (IsWindow()) { | |
94 VERIFY1(SUCCEEDED(CloseWindow())); | |
95 } | |
96 } | |
97 | |
98 // Enables the parent window and destroys this window. | |
99 // Enabling the parent window before destroying this one causes the parent | |
100 // window to get the focus and avoids a visible momentary lack of focus if we | |
101 // instead call SetFocus for the parent after the window is destroyed. | |
102 HRESULT InstallStoppedWnd::CloseWindow() { | |
103 ASSERT1(IsWindow()); | |
104 VERIFY1(::EnableWindow(parent_, true)); | |
105 | |
106 return DestroyWindow() ? S_OK : HRESULTFromLastError(); | |
107 } | |
108 | |
109 // Disables the parent window. | |
110 LRESULT InstallStoppedWnd::OnInitDialog(UINT, | |
111 WPARAM, | |
112 LPARAM, | |
113 BOOL& handled) { // NOLINT | |
114 VERIFY1(!::EnableWindow(parent_, false)); | |
115 VERIFY1(message_loop_->AddMessageFilter(this)); | |
116 handled = true; | |
117 return 1; | |
118 } | |
119 | |
120 // By letting the parent destroy this window, the parent to manage the entire | |
121 // lifetime of this window and avoid creating a synchronization problem by | |
122 // changing the value of IsInstallStoppedWindowPresent() during the middle of | |
123 // one of the parent's methods. | |
124 LRESULT InstallStoppedWnd::OnClickButton(WORD, | |
125 WORD id, | |
126 HWND, | |
127 BOOL& handled) { // NOLINT | |
128 CORE_LOG(L3, (_T("[InstallStoppedWnd::OnClickButton]"))); | |
129 ASSERT1(id == IDOK || id == IDCANCEL); | |
130 VERIFY1(::PostMessage(parent_, WM_INSTALL_STOPPED, id, 0)); | |
131 handled = true; | |
132 return 0; | |
133 } | |
134 | |
135 LRESULT InstallStoppedWnd::OnDestroy(UINT, | |
136 WPARAM, | |
137 LPARAM, | |
138 BOOL& handled) { // NOLINT | |
139 VERIFY1(message_loop_->RemoveMessageFilter(this)); | |
140 handled = true; | |
141 return 0; | |
142 } | |
143 | |
144 ProgressWnd::ProgressWnd(CMessageLoop* message_loop, HWND parent) | |
145 : CompleteWnd(IDD_PROGRESS, | |
146 ICC_STANDARD_CLASSES | ICC_PROGRESS_CLASS, | |
147 message_loop, | |
148 parent), | |
149 cur_state_(STATE_INIT), | |
150 events_sink_(NULL), | |
151 is_canceled_(false) { | |
152 CORE_LOG(L3, (_T("[ProgressWnd::ProgressWnd]"))); | |
153 } | |
154 | |
155 ProgressWnd::~ProgressWnd() { | |
156 CORE_LOG(L3, (_T("[ProgressWnd::~ProgressWnd]"))); | |
157 ASSERT1(!IsWindow()); | |
158 cur_state_ = STATE_END; | |
159 } | |
160 | |
161 void ProgressWnd::SetEventSink(ProgressWndEvents* ev) { | |
162 events_sink_ = ev; | |
163 CompleteWnd::SetEventSink(events_sink_); | |
164 } | |
165 | |
166 LRESULT ProgressWnd::OnInitDialog(UINT message, | |
167 WPARAM w_param, | |
168 LPARAM l_param, | |
169 BOOL& handled) { // NOLINT | |
170 CORE_LOG(L3, (_T("[ProgressWnd::OnInitDialog]"))); | |
171 UNREFERENCED_PARAMETER(message); | |
172 UNREFERENCED_PARAMETER(w_param); | |
173 UNREFERENCED_PARAMETER(l_param); | |
174 UNREFERENCED_PARAMETER(handled); | |
175 | |
176 InitializeDialog(); | |
177 | |
178 pause_resume_text_.reset(new StaticEx); | |
179 pause_resume_text_->SubclassWindow(GetDlgItem(IDC_PAUSE_RESUME_TEXT)); | |
180 | |
181 CString state_text; | |
182 VERIFY1(state_text.LoadString(IDS_INITIALIZING)); | |
183 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), state_text)); | |
184 VERIFY1(SUCCEEDED(SetMarqueeMode(true))); | |
185 VERIFY1(SUCCEEDED(ChangeControlState())); | |
186 | |
187 metrics_timer_.reset(new HighresTimer); | |
188 | |
189 return 1; // Let the system set the focus. | |
190 } | |
191 | |
192 // If closing is disabled, does not close the window. | |
193 // If in a completion state, the window is closed. | |
194 // Otherwise, the InstallStoppedWnd is displayed and the window is closed only | |
195 // if the user decides to cancel. | |
196 bool ProgressWnd::MaybeCloseWindow() { | |
197 if (!is_close_enabled()) { | |
198 return false; | |
199 } | |
200 | |
201 if (cur_state_ != STATE_COMPLETE_SUCCESS && | |
202 cur_state_ != STATE_COMPLETE_ERROR && | |
203 cur_state_ != STATE_COMPLETE_RESTART_BROWSER && | |
204 cur_state_ != STATE_COMPLETE_RESTART_ALL_BROWSERS && | |
205 cur_state_ != STATE_COMPLETE_REBOOT) { | |
206 // The UI is not in final state: ask the user to proceed with closing it. | |
207 // A modal dialog opens and sends a message back to this window to | |
208 // communicate the user decision. | |
209 install_stopped_wnd_.reset(new InstallStoppedWnd(message_loop(), *this)); | |
210 HWND hwnd = install_stopped_wnd_->Create(*this); | |
211 ASSERT1(hwnd); | |
212 if (hwnd) { | |
213 CString title; | |
214 VERIFY1(title.LoadString(IDS_INSTALLATION_STOPPED_WINDOW_TITLE)); | |
215 VERIFY1(install_stopped_wnd_->SetWindowText(title)); | |
216 | |
217 CString button_text; | |
218 VERIFY1(button_text.LoadString(IDS_RESUME_INSTALLATION)); | |
219 VERIFY1(::SetWindowText( | |
220 install_stopped_wnd_->GetDlgItem(IDOK), button_text)); | |
221 | |
222 VERIFY1(button_text.LoadString(IDS_CANCEL_INSTALLATION)); | |
223 VERIFY1(::SetWindowText( | |
224 install_stopped_wnd_->GetDlgItem(IDCANCEL), button_text)); | |
225 | |
226 CString s; | |
227 s.FormatMessage(IDS_INSTALL_STOPPED, bundle_name()); | |
228 VERIFY1(::SetWindowText( | |
229 install_stopped_wnd_->GetDlgItem(IDC_INSTALL_STOPPED_TEXT), s)); | |
230 | |
231 VERIFY1(install_stopped_wnd_->CenterWindow(*this)); | |
232 VERIFY1(!install_stopped_wnd_->ShowWindow(SW_SHOWDEFAULT)); | |
233 return false; | |
234 } | |
235 } | |
236 | |
237 VERIFY1(SUCCEEDED(CloseWindow())); | |
238 return true; | |
239 } | |
240 | |
241 LRESULT ProgressWnd::OnClickedButton(WORD notify_code, | |
242 WORD id, | |
243 HWND wnd_ctl, | |
244 BOOL& handled) { // NOLINT | |
245 CORE_LOG(L3, (_T("[ProgressWnd::OnClickedButton]"))); | |
246 ASSERT1(id == IDC_BUTTON1 || id == IDC_BUTTON2 || id == IDC_CLOSE); | |
247 ASSERT1(events_sink_); | |
248 | |
249 #pragma warning(push) | |
250 // C4061: enumerator 'xxx' in switch of enum 'yyy' is not explicitly handled by | |
251 // a case label. | |
252 #pragma warning(disable : 4061) | |
253 | |
254 switch (id) { | |
255 case IDC_BUTTON1: | |
256 // TODO(omaha): Consider doing something if the callbacks fail. | |
257 switch (cur_state_) { | |
258 case STATE_COMPLETE_RESTART_BROWSER: | |
259 ++metric_worker_ui_restart_browser_now_click; | |
260 VERIFY1(events_sink_->DoRestartBrowser(false, post_install_urls_)); | |
261 break; | |
262 case STATE_COMPLETE_RESTART_ALL_BROWSERS: | |
263 ++metric_worker_ui_restart_all_browsers_now_click; | |
264 VERIFY1(events_sink_->DoRestartBrowser(true, post_install_urls_)); | |
265 break; | |
266 case STATE_COMPLETE_REBOOT: | |
267 ++metric_worker_ui_reboot_now_click; | |
268 VERIFY1(events_sink_->DoReboot()); | |
269 break; | |
270 default: | |
271 ASSERT1(false); | |
272 } | |
273 break; | |
274 case IDC_BUTTON2: | |
275 switch (cur_state_) { | |
276 case STATE_COMPLETE_RESTART_BROWSER: | |
277 case STATE_COMPLETE_RESTART_ALL_BROWSERS: | |
278 case STATE_COMPLETE_REBOOT: | |
279 break; | |
280 default: | |
281 ASSERT1(false); | |
282 } | |
283 break; | |
284 case IDC_CLOSE: | |
285 switch (cur_state_) { | |
286 case STATE_COMPLETE_SUCCESS: | |
287 case STATE_COMPLETE_ERROR: | |
288 return CompleteWnd::OnClickedButton(notify_code, | |
289 id, | |
290 wnd_ctl, | |
291 handled); | |
292 break; | |
293 default: | |
294 ASSERT1(false); | |
295 } | |
296 break; | |
297 default: | |
298 ASSERT1(false); | |
299 } | |
300 #pragma warning(pop) | |
301 | |
302 // TODO(omaha3): In closing the Window here, we assume that none of the above | |
303 // code does anything that might delay the UI response. This should be true | |
304 // since we won't actually be restarting browsers, etc. from the UI. | |
305 handled = true; | |
306 VERIFY1(SUCCEEDED(CloseWindow())); | |
307 | |
308 return 0; | |
309 } | |
310 | |
311 LRESULT ProgressWnd::OnInstallStopped(UINT msg, | |
312 WPARAM wparam, | |
313 LPARAM, | |
314 BOOL& handled) { // NOLINT | |
315 CORE_LOG(L3, (_T("[ProgressWnd::OnInstallStopped]"))); | |
316 UNREFERENCED_PARAMETER(msg); | |
317 | |
318 install_stopped_wnd_.reset(); | |
319 | |
320 ASSERT1(msg == WM_INSTALL_STOPPED); | |
321 ASSERT1(wparam == IDOK || wparam == IDCANCEL); | |
322 // TODO(omaha): Swap the meaning of IDOK and IDCANCEL. IDCANCEL gets passed | |
323 // when the user hits the esc key. Successive esc presses result in the window | |
324 // disappearing. Instead, we would like the default (set in the .rc files) and | |
325 // esc key option to both resume. Changing this requires swapping all uses | |
326 // in this file as well as in the IDD_INSTALL_STOPPED definition. | |
327 // Maybe use different constants internally too since these values are used | |
328 // by different classes and ProgressWnd should not need to know how | |
329 // InstallStoppedWnd is implemented. | |
330 // It's possible this will also fix arrow key problem (http://b/1338787). | |
331 switch (wparam) { | |
332 case IDOK: | |
333 // TODO(omaha): Implement "Resume" here. | |
334 break; | |
335 case IDCANCEL: | |
336 HandleCancelRequest(); | |
337 break; | |
338 default: | |
339 ASSERT1(false); | |
340 break; | |
341 } | |
342 | |
343 handled = true; | |
344 return 0; | |
345 } | |
346 | |
347 void ProgressWnd::HandleCancelRequest() { | |
348 CString s; | |
349 VERIFY1(s.LoadString(IDS_CANCELING)); | |
350 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), s)); | |
351 | |
352 if (is_canceled_) { | |
353 return; | |
354 } | |
355 is_canceled_ = true; | |
356 | |
357 // The user has decided to cancel. | |
358 metric_worker_ui_cancel_ms.AddSample(metrics_timer_->GetElapsedMs()); | |
359 ++metric_worker_ui_cancels; | |
360 | |
361 if (events_sink_) { | |
362 events_sink_->DoCancel(); | |
363 } | |
364 } | |
365 | |
366 void ProgressWnd::OnCheckingForUpdate() { | |
367 CORE_LOG(L3, (_T("[ProgressWnd::OnCheckingForUpdate]"))); | |
368 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
369 if (!IsWindow()) { | |
370 return; | |
371 } | |
372 | |
373 cur_state_ = STATE_CHECKING_FOR_UPDATE; | |
374 | |
375 CString s; | |
376 VERIFY1(s.LoadString(IDS_WAITING_TO_CONNECT)); | |
377 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), s)); | |
378 | |
379 VERIFY1(SUCCEEDED(SetMarqueeMode(true))); | |
380 VERIFY1(SUCCEEDED(ChangeControlState())); | |
381 } | |
382 | |
383 void ProgressWnd::OnUpdateAvailable(const CString& app_name, | |
384 const CString& version_string) { | |
385 CORE_LOG(L3, (_T("[ProgressWnd::OnUpdateAvailable][%s][%s]"), | |
386 app_name, version_string)); | |
387 UNREFERENCED_PARAMETER(app_name); | |
388 UNREFERENCED_PARAMETER(version_string); | |
389 | |
390 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
391 if (!IsWindow()) { | |
392 return; | |
393 } | |
394 } | |
395 | |
396 void ProgressWnd::OnWaitingToDownload(const CString& app_name) { | |
397 CORE_LOG(L3, (_T("[ProgressWnd::OnWaitingToDownload][%s]"), app_name)); | |
398 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
399 if (!IsWindow()) { | |
400 return; | |
401 } | |
402 | |
403 cur_state_ = STATE_WAITING_TO_DOWNLOAD; | |
404 | |
405 CString s; | |
406 s.FormatMessage(IDS_WAITING_TO_DOWNLOAD, app_name); | |
407 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), s)); | |
408 | |
409 VERIFY1(SUCCEEDED(SetMarqueeMode(true))); | |
410 VERIFY1(SUCCEEDED(ChangeControlState())); | |
411 } | |
412 | |
413 // May be called repeatedly during download. | |
414 void ProgressWnd::OnDownloading(const CString& app_name, | |
415 int time_remaining_ms, | |
416 int pos) { | |
417 CORE_LOG(L5, (_T("[ProgressWnd::OnDownloading][%s][remaining ms=%d][pos=%d]"), | |
418 app_name, time_remaining_ms, pos)); | |
419 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
420 if (!IsWindow()) { | |
421 return; | |
422 } | |
423 | |
424 ASSERT1(0 <= pos && pos <= 100); | |
425 | |
426 cur_state_ = STATE_DOWNLOADING; | |
427 | |
428 // This resource is not included in the resource files since it's not used. | |
429 #if 0 | |
430 VERIFY1(s.LoadString(IDS_PAUSE)); | |
431 VERIFY1(::SetWindowText(GetDlgItem(IDC_PAUSE_RESUME_TEXT), s)); | |
432 #endif | |
433 | |
434 CString s; | |
435 | |
436 int time_remaining_sec = CeilingDivide(time_remaining_ms, kMsPerSec); | |
437 if (time_remaining_ms < 0) { | |
438 s.FormatMessage(IDS_WAITING_TO_DOWNLOAD, app_name); | |
439 } else if (time_remaining_ms == 0) { | |
440 s.FormatMessage(IDS_DOWNLOADING_COMPLETED, app_name); | |
441 } else if (time_remaining_sec < kSecPerMin) { | |
442 // Less than one minute remaining. | |
443 s.FormatMessage(IDS_DOWNLOADING_SHORT, app_name, time_remaining_sec); | |
444 } else if (time_remaining_sec < kSecondsPerHour) { | |
445 // Less than one hour remaining. | |
446 int time_remaining_minute = CeilingDivide(time_remaining_sec, kSecPerMin); | |
447 s.FormatMessage(IDS_DOWNLOADING_LONG, app_name, time_remaining_minute); | |
448 } else { | |
449 int time_remaining_hour = CeilingDivide(time_remaining_sec, | |
450 kSecondsPerHour); | |
451 s.FormatMessage(IDS_DOWNLOADING_VERY_LONG, app_name, time_remaining_hour); | |
452 } | |
453 | |
454 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), s)); | |
455 VERIFY1(SUCCEEDED(ChangeControlState())); | |
456 | |
457 // When the network is connecting keep the marquee moving, otherwise | |
458 // the user has no indication something is still going on. | |
459 // TODO(omaha): when resuming an incomplete download this will not work. | |
460 VERIFY1(SUCCEEDED(SetMarqueeMode(pos == 0))); | |
461 ::SendMessage(GetDlgItem(IDC_PROGRESS), PBM_SETPOS, pos, 0); | |
462 } | |
463 | |
464 void ProgressWnd::OnWaitingRetryDownload(const CString& app_name, | |
465 time64 next_retry_time) { | |
466 CORE_LOG(L5, (_T("[ProgressWnd::OnWaitingRetryDownload][%s][retry at:%llu]"), | |
467 app_name, next_retry_time)); | |
468 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
469 if (!IsWindow()) { | |
470 return; | |
471 } | |
472 | |
473 time64 now = GetCurrent100NSTime(); | |
474 if (now < next_retry_time) { | |
475 CString s; | |
476 int retry_time_in_sec = | |
477 static_cast<int>(CeilingDivide(next_retry_time - now, kSecsTo100ns)); | |
478 s.FormatMessage(IDS_DOWNLOAD_RETRY, app_name, retry_time_in_sec); | |
479 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), s)); | |
480 VERIFY1(SUCCEEDED(ChangeControlState())); | |
481 } | |
482 } | |
483 | |
484 void ProgressWnd::OnWaitingToInstall(const CString& app_name, | |
485 bool* can_start_install) { | |
486 CORE_LOG(L3, (_T("[ProgressWnd::OnWaitingToInstall][%s]"), app_name)); | |
487 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
488 ASSERT1(can_start_install); | |
489 if (!IsWindow()) { | |
490 return; | |
491 } | |
492 | |
493 if (STATE_WAITING_TO_INSTALL != cur_state_) { | |
494 cur_state_ = STATE_WAITING_TO_INSTALL; | |
495 | |
496 CString s; | |
497 s.FormatMessage(IDS_WAITING_TO_INSTALL, app_name); | |
498 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), s)); | |
499 | |
500 VERIFY1(SUCCEEDED(SetMarqueeMode(true))); | |
501 VERIFY1(SUCCEEDED(ChangeControlState())); | |
502 } | |
503 | |
504 // If we want to instead close the window and start install, call | |
505 // CloseInstallStoppedWindow() and return *can_start_install = true. | |
506 *can_start_install = !IsInstallStoppedWindowPresent(); | |
507 } | |
508 | |
509 // May be called repeatedly during install. | |
510 void ProgressWnd::OnInstalling(const CString& app_name) { | |
511 CORE_LOG(L5, (_T("[ProgressWnd::OnInstalling][%s]"), app_name)); | |
512 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
513 if (!IsWindow()) { | |
514 return; | |
515 } | |
516 | |
517 // TODO(omaha3): This can now occur because installs are not gated. | |
518 // ASSERT1(!IsInstallStoppedWindowPresent()); | |
519 | |
520 if (STATE_INSTALLING != cur_state_) { | |
521 cur_state_ = STATE_INSTALLING; | |
522 | |
523 CString s; | |
524 s.FormatMessage(IDS_INSTALLING, app_name); | |
525 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), s)); | |
526 | |
527 VERIFY1(SUCCEEDED(SetMarqueeMode(true))); | |
528 VERIFY1(SUCCEEDED(ChangeControlState())); | |
529 } | |
530 } | |
531 | |
532 // TODO(omaha): Should this message display the app name or bundle name? Is the | |
533 // entire bundle paused? | |
534 void ProgressWnd::OnPause() { | |
535 CORE_LOG(L3, (_T("[ProgressWnd::OnPause]"))); | |
536 ASSERT(false, (_T("These strings are not in the .rc files."))); | |
537 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
538 if (!IsWindow()) { | |
539 return; | |
540 } | |
541 | |
542 cur_state_ = STATE_PAUSED; | |
543 | |
544 // These resources are not included in resource files since they are not used. | |
545 #if 0 | |
546 CString s; | |
547 s.FormatMessage(IDS_DOWNLOAD_PAUSED, bundle_name()); | |
548 VERIFY1(::SetWindowText(GetDlgItem(IDC_INSTALLER_STATE_TEXT), s)); | |
549 | |
550 VERIFY1(s.LoadString(IDS_RESUME)); | |
551 VERIFY1(::SetWindowText(GetDlgItem(IDC_PAUSE_RESUME_TEXT), s)); | |
552 #endif | |
553 | |
554 // TODO(omaha): implement time left. | |
555 | |
556 VERIFY1(SUCCEEDED(ChangeControlState())); | |
557 } | |
558 | |
559 void ProgressWnd::DeterminePostInstallUrls(const ObserverCompletionInfo& info) { | |
560 ASSERT1(post_install_urls_.empty()); | |
561 post_install_urls_.clear(); | |
562 | |
563 for (size_t i = 0; i < info.apps_info.size(); ++i) { | |
564 const AppCompletionInfo& app_info = info.apps_info[i]; | |
565 if (!app_info.post_install_url.IsEmpty() && | |
566 (app_info.completion_code == COMPLETION_CODE_RESTART_ALL_BROWSERS || | |
567 app_info.completion_code == COMPLETION_CODE_RESTART_BROWSER)) { | |
568 post_install_urls_.push_back(app_info.post_install_url); | |
569 } | |
570 } | |
571 ASSERT1(!post_install_urls_.empty()); | |
572 } | |
573 | |
574 // TODO(omaha): We can eliminate this function is we have a better UI that can | |
575 // show compeltion status for each app in the bundle. | |
576 // | |
577 // Overall completion code is determined by apps' completion codes and bundle | |
578 // completion code. If bundle installation fails or installation completed after | |
579 // a cancel is attempted, returns bundle completion code. | |
580 // Otherwise the app's completion code that has the greatest priority is | |
581 // returned. | |
582 CompletionCodes ProgressWnd::GetBundleOverallCompletionCode( | |
583 const ObserverCompletionInfo& info) const { | |
584 if (info.completion_code == COMPLETION_CODE_ERROR || | |
585 info.completion_code == COMPLETION_CODE_INSTALL_FINISHED_BEFORE_CANCEL) { | |
586 return info.completion_code; | |
587 } | |
588 | |
589 ASSERT1(info.completion_code == COMPLETION_CODE_SUCCESS); | |
590 | |
591 CompletionCodes overall_completion_code = | |
592 kCompletionCodesActionPriority[0]; | |
593 for (size_t i = 0; i < info.apps_info.size(); ++i) { | |
594 if (GetActionPriority(overall_completion_code) < | |
595 GetActionPriority(info.apps_info[i].completion_code)) { | |
596 overall_completion_code = info.apps_info[i].completion_code; | |
597 } | |
598 } | |
599 | |
600 return overall_completion_code; | |
601 } | |
602 | |
603 // TODO(omaha3): How should we display the restart browser and reboot messages | |
604 // when multiple apps are being installed, some of which may have failed? Should | |
605 // we use the app name or bundle name? | |
606 void ProgressWnd::OnComplete(const ObserverCompletionInfo& observer_info) { | |
607 CORE_LOG(L3, (_T("[ProgressWnd::OnComplete][%s]"), observer_info.ToString())); | |
608 ASSERT1(thread_id() == ::GetCurrentThreadId()); | |
609 | |
610 if (!CompleteWnd::OnComplete()) { | |
611 return; | |
612 } | |
613 | |
614 // Close the 'Install Stop' window if it is on the screen. | |
615 // TODO(omaha3): This had been before all main dialog UI. Make sure looks OK. | |
616 CloseInstallStoppedWindow(); | |
617 | |
618 // TODO(omaha3): Do we want to avoid launching commands during an interactive | |
619 // /ua update? If so, we'll need to handle that somehow. Using the observer | |
620 // handles the silent update and install cases as well as the OnDemand case. | |
621 bool launch_commands_succeeded = LaunchCmdLines(observer_info); | |
622 | |
623 CString s; | |
624 CompletionCodes overall_completion_code = | |
625 GetBundleOverallCompletionCode(observer_info); | |
626 CORE_LOG(L3, (_T("[overall completion code: %d]"), overall_completion_code)); | |
627 switch (overall_completion_code) { | |
628 case COMPLETION_CODE_SUCCESS: | |
629 case COMPLETION_CODE_LAUNCH_COMMAND: | |
630 case COMPLETION_CODE_INSTALL_FINISHED_BEFORE_CANCEL: | |
631 cur_state_ = STATE_COMPLETE_SUCCESS; | |
632 | |
633 // TODO(omaha): Do not inherit from CompleteWnd once we have the new | |
634 // bundle-supporting UI. Among other things, calling | |
635 // DisplayCompletionDialog causes second call to OmahaWnd::OnComplete(). | |
636 CompleteWnd::DisplayCompletionDialog(true, | |
637 observer_info.completion_text, | |
638 observer_info.help_url); | |
639 break; | |
640 case COMPLETION_CODE_ERROR: | |
641 // If all apps are canceled, no need to display any dialog. | |
642 if (AreAllAppsCanceled(observer_info.apps_info)) { | |
643 VERIFY1(SUCCEEDED(CloseWindow())); | |
644 return; | |
645 } else { | |
646 cur_state_ = STATE_COMPLETE_ERROR; | |
647 CompleteWnd::DisplayCompletionDialog(false, | |
648 observer_info.completion_text, | |
649 observer_info.help_url); | |
650 } | |
651 break; | |
652 case COMPLETION_CODE_RESTART_ALL_BROWSERS: | |
653 cur_state_ = STATE_COMPLETE_RESTART_ALL_BROWSERS; | |
654 VERIFY1(s.LoadString(IDS_RESTART_NOW)); | |
655 VERIFY1(::SetWindowText(GetDlgItem(IDC_BUTTON1), s)); | |
656 VERIFY1(s.LoadString(IDS_RESTART_LATER)); | |
657 VERIFY1(::SetWindowText(GetDlgItem(IDC_BUTTON2), s)); | |
658 s.FormatMessage(IDS_TEXT_RESTART_ALL_BROWSERS, bundle_name()); | |
659 VERIFY1(::SetWindowText(GetDlgItem(IDC_COMPLETE_TEXT), s)); | |
660 DeterminePostInstallUrls(observer_info); | |
661 ++metric_worker_ui_restart_all_browsers_buttons_displayed; | |
662 break; | |
663 case COMPLETION_CODE_RESTART_BROWSER: | |
664 cur_state_ = STATE_COMPLETE_RESTART_BROWSER; | |
665 VERIFY1(s.LoadString(IDS_RESTART_NOW)); | |
666 VERIFY1(::SetWindowText(GetDlgItem(IDC_BUTTON1), s)); | |
667 VERIFY1(s.LoadString(IDS_RESTART_LATER)); | |
668 VERIFY1(::SetWindowText(GetDlgItem(IDC_BUTTON2), s)); | |
669 s.FormatMessage(IDS_TEXT_RESTART_BROWSER, bundle_name()); | |
670 VERIFY1(::SetWindowText(GetDlgItem(IDC_COMPLETE_TEXT), s)); | |
671 DeterminePostInstallUrls(observer_info); | |
672 ++metric_worker_ui_restart_browser_buttons_displayed; | |
673 break; | |
674 case COMPLETION_CODE_REBOOT: | |
675 ASSERT(false, (_T("The button actions are not implemented."))); | |
676 cur_state_ = STATE_COMPLETE_REBOOT; | |
677 VERIFY1(s.LoadString(IDS_RESTART_NOW)); | |
678 VERIFY1(::SetWindowText(GetDlgItem(IDC_BUTTON1), s)); | |
679 VERIFY1(s.LoadString(IDS_RESTART_LATER)); | |
680 VERIFY1(::SetWindowText(GetDlgItem(IDC_BUTTON2), s)); | |
681 s.FormatMessage(IDS_TEXT_RESTART_COMPUTER, bundle_name()); | |
682 VERIFY1(::SetWindowText(GetDlgItem(IDC_COMPLETE_TEXT), s)); | |
683 ++metric_worker_ui_reboot_buttons_displayed; | |
684 break; | |
685 // TODO(omaha3): We may be able to eliminate these by having the caller | |
686 // specify the appropriate success text. That is the only difference from | |
687 // the COMPLETION_CODE_SUCCESS case. Alternatively, we can make a decision | |
688 // in this class based on, for example, whether the browser is supported. | |
689 case COMPLETION_CODE_RESTART_ALL_BROWSERS_NOTICE_ONLY: | |
690 cur_state_ = STATE_COMPLETE_SUCCESS; | |
691 s.FormatMessage(IDS_TEXT_RESTART_ALL_BROWSERS, bundle_name()); | |
692 CompleteWnd::DisplayCompletionDialog(true, s, observer_info.help_url); | |
693 break; | |
694 case COMPLETION_CODE_REBOOT_NOTICE_ONLY: | |
695 cur_state_ = STATE_COMPLETE_SUCCESS; | |
696 s.FormatMessage(IDS_TEXT_RESTART_COMPUTER, bundle_name()); | |
697 CompleteWnd::DisplayCompletionDialog(true, s, observer_info.help_url); | |
698 break; | |
699 case COMPLETION_CODE_RESTART_BROWSER_NOTICE_ONLY: | |
700 cur_state_ = STATE_COMPLETE_SUCCESS; | |
701 s.FormatMessage(IDS_TEXT_RESTART_BROWSER, bundle_name()); | |
702 CompleteWnd::DisplayCompletionDialog(true, s, observer_info.help_url); | |
703 break; | |
704 case COMPLETION_CODE_EXIT_SILENTLY_ON_LAUNCH_COMMAND: | |
705 cur_state_ = STATE_COMPLETE_SUCCESS; | |
706 if (launch_commands_succeeded) { | |
707 VERIFY1(SUCCEEDED(CloseWindow())); | |
708 return; | |
709 } | |
710 | |
711 CompleteWnd::DisplayCompletionDialog(true, | |
712 observer_info.completion_text, | |
713 observer_info.help_url); | |
714 break; | |
715 case COMPLETION_CODE_EXIT_SILENTLY: | |
716 cur_state_ = STATE_COMPLETE_SUCCESS; | |
717 VERIFY1(SUCCEEDED(CloseWindow())); | |
718 return; | |
719 default: | |
720 ASSERT1(false); | |
721 break; | |
722 } | |
723 | |
724 VERIFY1(SUCCEEDED(ChangeControlState())); | |
725 } | |
726 | |
727 HRESULT ProgressWnd::LaunchCmdLine(const AppCompletionInfo& app_info) { | |
728 CORE_LOG(L3, (_T("[ProgressWnd::LaunchCmdLine][%s]"), | |
729 app_info.post_install_launch_command_line)); | |
730 if (app_info.post_install_launch_command_line.IsEmpty()) { | |
731 return S_OK; | |
732 } | |
733 | |
734 if (app_info.completion_code != COMPLETION_CODE_LAUNCH_COMMAND && | |
735 app_info.completion_code != | |
736 COMPLETION_CODE_EXIT_SILENTLY_ON_LAUNCH_COMMAND) { | |
737 CORE_LOG(LW, (_T("Launch command line [%s] is not empty but completion ") | |
738 _T("code [%d] doesn't require a launch"), | |
739 app_info.post_install_launch_command_line.GetString(), | |
740 app_info.completion_code)); | |
741 return S_OK; | |
742 } | |
743 | |
744 ASSERT1(SUCCEEDED(app_info.error_code)); | |
745 ASSERT1(!app_info.is_noupdate); | |
746 | |
747 HRESULT hr = goopdate_utils::LaunchCmdLine( | |
748 is_machine(), app_info.post_install_launch_command_line); | |
749 if (FAILED(hr)) { | |
750 CORE_LOG(LE, (_T("[goopdate_utils::LaunchCmdLine failed][0x%x]"), hr)); | |
751 return hr; | |
752 } | |
753 | |
754 return S_OK; | |
755 } | |
756 | |
757 bool ProgressWnd::LaunchCmdLines(const ObserverCompletionInfo& info) { | |
758 bool result = true; | |
759 | |
760 CORE_LOG(L3, (_T("[ProgressWnd::LaunchCmdLines]"))); | |
761 for (size_t i = 0; i < info.apps_info.size(); ++i) { | |
762 const AppCompletionInfo& app_info = info.apps_info[i]; | |
763 if (FAILED(app_info.error_code)) { | |
764 continue; | |
765 } | |
766 result &= SUCCEEDED(LaunchCmdLine(app_info)); | |
767 VERIFY1(result); | |
768 } | |
769 | |
770 return result; | |
771 } | |
772 | |
773 HRESULT ProgressWnd::ChangeControlState() { | |
774 for (size_t i = 0; i != arraysize(ctls_); ++i) { | |
775 const ControlState& ctl_state = ctls_[i]; | |
776 SetControlAttributes(ctl_state.id_, ctl_state.attr_[cur_state_]); | |
777 } | |
778 return S_OK; | |
779 } | |
780 | |
781 HRESULT ProgressWnd::SetMarqueeMode(bool is_marquee) { | |
782 if (!SystemInfo::IsRunningOnXPOrLater()) { | |
783 // Marquee is not supported on OSes below XP. | |
784 return S_OK; | |
785 } | |
786 | |
787 HWND progress_bar = GetDlgItem(IDC_PROGRESS); | |
788 if (!progress_bar) { | |
789 return GOOPDATE_E_UI_INTERNAL_ERROR; | |
790 } | |
791 | |
792 LONG style = ::GetWindowLong(progress_bar, GWL_STYLE); | |
793 if (!style) { | |
794 return HRESULTFromLastError(); | |
795 } | |
796 | |
797 if (is_marquee) { | |
798 if (style & PBS_MARQUEE) { | |
799 return S_OK; | |
800 } | |
801 | |
802 style |= PBS_MARQUEE; | |
803 style = ::SetWindowLong(progress_bar, GWL_STYLE, style); | |
804 if (!style) { | |
805 return HRESULTFromLastError(); | |
806 } | |
807 | |
808 bool result = ::SendMessage(progress_bar, PBM_SETMARQUEE, | |
809 is_marquee, kMarqueeModeUpdatesMs) != 0; | |
810 return result ? S_OK : GOOPDATE_E_UI_INTERNAL_ERROR; | |
811 } else { | |
812 if (!(style & PBS_MARQUEE)) { | |
813 return S_OK; | |
814 } | |
815 | |
816 style &= ~PBS_MARQUEE; | |
817 style = ::SetWindowLong(progress_bar, GWL_STYLE, style); | |
818 if (!style) { | |
819 return HRESULTFromLastError(); | |
820 } | |
821 return S_OK; | |
822 } | |
823 } | |
824 | |
825 bool ProgressWnd::IsInstallStoppedWindowPresent() { | |
826 return install_stopped_wnd_.get() && install_stopped_wnd_->IsWindow(); | |
827 } | |
828 | |
829 bool ProgressWnd::CloseInstallStoppedWindow() { | |
830 if (IsInstallStoppedWindowPresent()) { | |
831 VERIFY1(SUCCEEDED(install_stopped_wnd_->CloseWindow())); | |
832 install_stopped_wnd_.reset(); | |
833 return true; | |
834 } else { | |
835 return false; | |
836 } | |
837 } | |
838 | |
839 } // namespace omaha | |
840 | |
OLD | NEW |