| 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 |