update_screen.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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.
4
5#include "chrome/browser/chromeos/login/screens/update_screen.h"
6
7#include <algorithm>
8
9#include "base/bind.h"
10#include "base/command_line.h"
11#include "base/file_util.h"
12#include "base/logging.h"
13#include "base/message_loop/message_loop.h"
14#include "base/threading/thread_restrictions.h"
15#include "chrome/browser/chromeos/login/screens/error_screen.h"
16#include "chrome/browser/chromeos/login/screens/screen_observer.h"
17#include "chrome/browser/chromeos/login/screens/update_screen_actor.h"
18#include "chrome/browser/chromeos/login/startup_utils.h"
19#include "chrome/browser/chromeos/login/wizard_controller.h"
20#include "chromeos/chromeos_switches.h"
21#include "chromeos/dbus/dbus_thread_manager.h"
22#include "chromeos/network/network_state.h"
23#include "content/public/browser/browser_thread.h"
24
25using content::BrowserThread;
26
27namespace chromeos {
28
29namespace {
30
31// Progress bar stages. Each represents progress bar value
32// at the beginning of each stage.
33// TODO(nkostylev): Base stage progress values on approximate time.
34// TODO(nkostylev): Animate progress during each state.
35const int kBeforeUpdateCheckProgress = 7;
36const int kBeforeDownloadProgress = 14;
37const int kBeforeVerifyingProgress = 74;
38const int kBeforeFinalizingProgress = 81;
39const int kProgressComplete = 100;
40
41// Defines what part of update progress does download part takes.
42const int kDownloadProgressIncrement = 60;
43
44const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline";
45
46// Minimum timestep between two consecutive measurements for the
47// download rate.
48const base::TimeDelta kMinTimeStep = base::TimeDelta::FromSeconds(1);
49
50// Smooth factor that is used for the average downloading speed
51// estimation.
52// avg_speed = smooth_factor * cur_speed + (1.0 - smooth_factor) * avg_speed.
53const double kDownloadSpeedSmoothFactor = 0.1;
54
55// Minumum allowed value for the average downloading speed.
56const double kDownloadAverageSpeedDropBound = 1e-8;
57
58// An upper bound for possible downloading time left estimations.
59const double kMaxTimeLeft = 24 * 60 * 60;
60
61// Invoked from call to RequestUpdateCheck upon completion of the DBus call.
62void StartUpdateCallback(UpdateScreen* screen,
63                         UpdateEngineClient::UpdateCheckResult result) {
64  VLOG(1) << "Callback from RequestUpdateCheck, result " << result;
65  if (UpdateScreen::HasInstance(screen)) {
66    if (result == UpdateEngineClient::UPDATE_RESULT_SUCCESS)
67      screen->SetIgnoreIdleStatus(false);
68    else
69      screen->ExitUpdate(UpdateScreen::REASON_UPDATE_INIT_FAILED);
70  }
71}
72
73}  // anonymous namespace
74
75// static
76UpdateScreen::InstanceSet& UpdateScreen::GetInstanceSet() {
77  CR_DEFINE_STATIC_LOCAL(std::set<UpdateScreen*>, instance_set, ());
78  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));  // not threadsafe.
79  return instance_set;
80}
81
82// static
83bool UpdateScreen::HasInstance(UpdateScreen* inst) {
84  InstanceSet& instance_set = GetInstanceSet();
85  InstanceSet::iterator found = instance_set.find(inst);
86  return (found != instance_set.end());
87}
88
89UpdateScreen::UpdateScreen(
90    ScreenObserver* screen_observer,
91    UpdateScreenActor* actor)
92    : WizardScreen(screen_observer),
93      state_(STATE_IDLE),
94      reboot_check_delay_(0),
95      is_checking_for_update_(true),
96      is_downloading_update_(false),
97      is_ignore_update_deadlines_(false),
98      is_shown_(false),
99      ignore_idle_status_(true),
100      actor_(actor),
101      is_first_detection_notification_(true),
102      is_first_portal_notification_(true),
103      weak_factory_(this) {
104  DCHECK(actor_);
105  if (actor_)
106    actor_->SetDelegate(this);
107  GetInstanceSet().insert(this);
108}
109
110UpdateScreen::~UpdateScreen() {
111  DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
112  NetworkPortalDetector::Get()->RemoveObserver(this);
113  GetInstanceSet().erase(this);
114  if (actor_)
115    actor_->SetDelegate(NULL);
116}
117
118void UpdateScreen::UpdateStatusChanged(
119    const UpdateEngineClient::Status& status) {
120  if (!actor_)
121    return;
122
123  if (is_checking_for_update_ &&
124      status.status > UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE) {
125    is_checking_for_update_ = false;
126  }
127  if (ignore_idle_status_ && status.status >
128      UpdateEngineClient::UPDATE_STATUS_IDLE) {
129    ignore_idle_status_ = false;
130  }
131
132  switch (status.status) {
133    case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE:
134      // Do nothing in these cases, we don't want to notify the user of the
135      // check unless there is an update.
136      break;
137    case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE:
138      MakeSureScreenIsShown();
139      actor_->SetProgress(kBeforeDownloadProgress);
140      actor_->ShowEstimatedTimeLeft(false);
141      if (!HasCriticalUpdate()) {
142        VLOG(1) << "Noncritical update available: " << status.new_version;
143        ExitUpdate(REASON_UPDATE_NON_CRITICAL);
144      } else {
145        VLOG(1) << "Critical update available: " << status.new_version;
146        actor_->SetProgressMessage(
147            UpdateScreenActor::PROGRESS_MESSAGE_UPDATE_AVAILABLE);
148        actor_->ShowProgressMessage(true);
149        actor_->ShowCurtain(false);
150      }
151      break;
152    case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING:
153      {
154        MakeSureScreenIsShown();
155        if (!is_downloading_update_) {
156          // Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE
157          // we need to is update critical on first downloading notification.
158          is_downloading_update_ = true;
159          download_start_time_ = download_last_time_ = base::Time::Now();
160          download_start_progress_ = status.download_progress;
161          download_last_progress_ = status.download_progress;
162          is_download_average_speed_computed_ = false;
163          download_average_speed_ = 0.0;
164          if (!HasCriticalUpdate()) {
165            VLOG(1) << "Non-critical update available: " << status.new_version;
166            ExitUpdate(REASON_UPDATE_NON_CRITICAL);
167          } else {
168            VLOG(1) << "Critical update available: " << status.new_version;
169            actor_->SetProgressMessage(
170                UpdateScreenActor::PROGRESS_MESSAGE_INSTALLING_UPDATE);
171            actor_->ShowProgressMessage(true);
172            actor_->ShowCurtain(false);
173          }
174        }
175        UpdateDownloadingStats(status);
176      }
177      break;
178    case UpdateEngineClient::UPDATE_STATUS_VERIFYING:
179      MakeSureScreenIsShown();
180      actor_->SetProgress(kBeforeVerifyingProgress);
181      actor_->SetProgressMessage(UpdateScreenActor::PROGRESS_MESSAGE_VERIFYING);
182      actor_->ShowProgressMessage(true);
183      break;
184    case UpdateEngineClient::UPDATE_STATUS_FINALIZING:
185      MakeSureScreenIsShown();
186      actor_->SetProgress(kBeforeFinalizingProgress);
187      actor_->SetProgressMessage(
188          UpdateScreenActor::PROGRESS_MESSAGE_FINALIZING);
189      actor_->ShowProgressMessage(true);
190      break;
191    case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT:
192      MakeSureScreenIsShown();
193      actor_->SetProgress(kProgressComplete);
194      actor_->ShowEstimatedTimeLeft(false);
195      if (HasCriticalUpdate()) {
196        actor_->ShowCurtain(false);
197        VLOG(1) << "Initiate reboot after update";
198        DBusThreadManager::Get()->GetUpdateEngineClient()->RebootAfterUpdate();
199        reboot_timer_.Start(FROM_HERE,
200                            base::TimeDelta::FromSeconds(reboot_check_delay_),
201                            this,
202                            &UpdateScreen::OnWaitForRebootTimeElapsed);
203      } else {
204        ExitUpdate(REASON_UPDATE_NON_CRITICAL);
205      }
206      break;
207    case UpdateEngineClient::UPDATE_STATUS_ATTEMPTING_ROLLBACK:
208      VLOG(1) << "Attempting rollback";
209      break;
210    case UpdateEngineClient::UPDATE_STATUS_IDLE:
211      if (ignore_idle_status_) {
212        // It is first IDLE status that is sent before we initiated the check.
213        break;
214      }
215      // else no break
216    case UpdateEngineClient::UPDATE_STATUS_ERROR:
217    case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT:
218      ExitUpdate(REASON_UPDATE_ENDED);
219      break;
220    default:
221      NOTREACHED();
222      break;
223  }
224}
225
226void UpdateScreen::OnPortalDetectionCompleted(
227    const NetworkState* network,
228    const NetworkPortalDetector::CaptivePortalState& state) {
229  LOG(WARNING) << "UpdateScreen::PortalDetectionCompleted(): "
230               << "network=" << (network ? network->path() : "") << ", "
231               << "state.status=" << state.status << ", "
232               << "state.response_code=" << state.response_code;
233
234  // Wait for the sane detection results.
235  if (network &&
236      state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN) {
237    return;
238  }
239
240  // Restart portal detection for the first notification about offline state.
241  if ((!network ||
242       state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE) &&
243      is_first_detection_notification_) {
244    is_first_detection_notification_ = false;
245    base::MessageLoop::current()->PostTask(
246        FROM_HERE,
247        base::Bind(
248            base::IgnoreResult(&NetworkPortalDetector::StartDetectionIfIdle),
249            base::Unretained(NetworkPortalDetector::Get())));
250    return;
251  }
252  is_first_detection_notification_ = false;
253
254  NetworkPortalDetector::CaptivePortalStatus status = state.status;
255  if (state_ == STATE_ERROR) {
256    // In the case of online state hide error message and proceed to
257    // the update stage. Otherwise, update error message content.
258    if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE)
259      StartUpdateCheck();
260    else
261      UpdateErrorMessage(network, status);
262  } else if (state_ == STATE_FIRST_PORTAL_CHECK) {
263    // In the case of online state immediately proceed to the update
264    // stage. Otherwise, prepare and show error message.
265    if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) {
266      StartUpdateCheck();
267    } else {
268      UpdateErrorMessage(network, status);
269      ShowErrorMessage();
270    }
271  }
272}
273
274void UpdateScreen::StartNetworkCheck() {
275  // If portal detector is enabled and portal detection before AU is
276  // allowed, initiate network state check. Otherwise, directly
277  // proceed to update.
278  if (!NetworkPortalDetector::Get()->IsEnabled()) {
279    StartUpdateCheck();
280    return;
281  }
282  state_ = STATE_FIRST_PORTAL_CHECK;
283  is_first_detection_notification_ = true;
284  is_first_portal_notification_ = true;
285  NetworkPortalDetector::Get()->AddAndFireObserver(this);
286}
287
288void UpdateScreen::CancelUpdate() {
289  VLOG(1) << "Forced update cancel";
290  ExitUpdate(REASON_UPDATE_CANCELED);
291}
292
293void UpdateScreen::Show() {
294  is_shown_ = true;
295  if (actor_) {
296    actor_->Show();
297    actor_->SetProgress(kBeforeUpdateCheckProgress);
298  }
299}
300
301void UpdateScreen::Hide() {
302  if (actor_)
303    actor_->Hide();
304  is_shown_ = false;
305}
306
307std::string UpdateScreen::GetName() const {
308  return WizardController::kUpdateScreenName;
309}
310
311void UpdateScreen::PrepareToShow() {
312  if (actor_)
313    actor_->PrepareToShow();
314}
315
316void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) {
317  DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
318  NetworkPortalDetector::Get()->RemoveObserver(this);
319
320  switch (reason) {
321    case REASON_UPDATE_CANCELED:
322      get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE);
323      break;
324    case REASON_UPDATE_INIT_FAILED:
325      get_screen_observer()->OnExit(
326          ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE);
327      break;
328    case REASON_UPDATE_NON_CRITICAL:
329    case REASON_UPDATE_ENDED:
330      {
331        UpdateEngineClient* update_engine_client =
332            DBusThreadManager::Get()->GetUpdateEngineClient();
333        switch (update_engine_client->GetLastStatus().status) {
334          case UpdateEngineClient::UPDATE_STATUS_ATTEMPTING_ROLLBACK:
335            break;
336          case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE:
337          case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT:
338          case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING:
339          case UpdateEngineClient::UPDATE_STATUS_FINALIZING:
340          case UpdateEngineClient::UPDATE_STATUS_VERIFYING:
341            DCHECK(!HasCriticalUpdate());
342            // Noncritical update, just exit screen as if there is no update.
343            // no break
344          case UpdateEngineClient::UPDATE_STATUS_IDLE:
345            get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE);
346            break;
347          case UpdateEngineClient::UPDATE_STATUS_ERROR:
348          case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT:
349            get_screen_observer()->OnExit(is_checking_for_update_ ?
350                ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE :
351                ScreenObserver::UPDATE_ERROR_UPDATING);
352            break;
353          default:
354            NOTREACHED();
355        }
356      }
357      break;
358    default:
359      NOTREACHED();
360  }
361}
362
363void UpdateScreen::OnWaitForRebootTimeElapsed() {
364  LOG(ERROR) << "Unable to reboot - asking user for a manual reboot.";
365  MakeSureScreenIsShown();
366  if (actor_)
367    actor_->ShowManualRebootInfo();
368}
369
370void UpdateScreen::MakeSureScreenIsShown() {
371  if (!is_shown_)
372    get_screen_observer()->ShowCurrentScreen();
373}
374
375void UpdateScreen::SetRebootCheckDelay(int seconds) {
376  if (seconds <= 0)
377    reboot_timer_.Stop();
378  DCHECK(!reboot_timer_.IsRunning());
379  reboot_check_delay_ = seconds;
380}
381
382void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status) {
383  ignore_idle_status_ = ignore_idle_status;
384}
385
386void UpdateScreen::UpdateDownloadingStats(
387    const UpdateEngineClient::Status& status) {
388  if (!actor_)
389    return;
390  base::Time download_current_time = base::Time::Now();
391  if (download_current_time >= download_last_time_ + kMinTimeStep) {
392    // Estimate downloading rate.
393    double progress_delta =
394        std::max(status.download_progress - download_last_progress_, 0.0);
395    double time_delta =
396        (download_current_time - download_last_time_).InSecondsF();
397    double download_rate = status.new_size * progress_delta / time_delta;
398
399    download_last_time_ = download_current_time;
400    download_last_progress_ = status.download_progress;
401
402    // Estimate time left.
403    double progress_left = std::max(1.0 - status.download_progress, 0.0);
404    if (!is_download_average_speed_computed_) {
405      download_average_speed_ = download_rate;
406      is_download_average_speed_computed_ = true;
407    }
408    download_average_speed_ =
409        kDownloadSpeedSmoothFactor * download_rate +
410        (1.0 - kDownloadSpeedSmoothFactor) * download_average_speed_;
411    if (download_average_speed_ < kDownloadAverageSpeedDropBound) {
412      time_delta =
413          (download_current_time - download_start_time_).InSecondsF();
414      download_average_speed_ =
415          status.new_size *
416          (status.download_progress - download_start_progress_) /
417          time_delta;
418    }
419    double work_left = progress_left * status.new_size;
420    double time_left = work_left / download_average_speed_;
421    // |time_left| may be large enough or even +infinity. So we must
422    // |bound possible estimations.
423    time_left = std::min(time_left, kMaxTimeLeft);
424
425    actor_->ShowEstimatedTimeLeft(true);
426    actor_->SetEstimatedTimeLeft(
427        base::TimeDelta::FromSeconds(static_cast<int64>(time_left)));
428  }
429
430  int download_progress = static_cast<int>(
431      status.download_progress * kDownloadProgressIncrement);
432  actor_->SetProgress(kBeforeDownloadProgress + download_progress);
433}
434
435bool UpdateScreen::HasCriticalUpdate() {
436  if (is_ignore_update_deadlines_)
437    return true;
438
439  std::string deadline;
440  // Checking for update flag file causes us to do blocking IO on UI thread.
441  // Temporarily allow it until we fix http://crosbug.com/11106
442  base::ThreadRestrictions::ScopedAllowIO allow_io;
443  base::FilePath update_deadline_file_path(kUpdateDeadlineFile);
444  if (!base::ReadFileToString(update_deadline_file_path, &deadline) ||
445      deadline.empty()) {
446    return false;
447  }
448
449  // TODO(dpolukhin): Analyze file content. Now we can just assume that
450  // if the file exists and not empty, there is critical update.
451  return true;
452}
453
454void UpdateScreen::OnActorDestroyed(UpdateScreenActor* actor) {
455  if (actor_ == actor)
456    actor_ = NULL;
457}
458
459void UpdateScreen::OnConnectToNetworkRequested(
460    const std::string& service_path) {
461  if (state_ == STATE_ERROR) {
462    LOG(WARNING) << "Hiding error message since AP was reselected";
463    StartUpdateCheck();
464  }
465}
466
467ErrorScreen* UpdateScreen::GetErrorScreen() {
468  return get_screen_observer()->GetErrorScreen();
469}
470
471void UpdateScreen::StartUpdateCheck() {
472  NetworkPortalDetector::Get()->RemoveObserver(this);
473  if (state_ == STATE_ERROR)
474    HideErrorMessage();
475  state_ = STATE_UPDATE;
476  DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this);
477  VLOG(1) << "Initiate update check";
478  DBusThreadManager::Get()->GetUpdateEngineClient()->RequestUpdateCheck(
479      base::Bind(StartUpdateCallback, this));
480}
481
482void UpdateScreen::ShowErrorMessage() {
483  LOG(WARNING) << "UpdateScreen::ShowErrorMessage()";
484  state_ = STATE_ERROR;
485  GetErrorScreen()->SetUIState(ErrorScreen::UI_STATE_UPDATE);
486  get_screen_observer()->ShowErrorScreen();
487}
488
489void UpdateScreen::HideErrorMessage() {
490  LOG(WARNING) << "UpdateScreen::HideErrorMessage()";
491  get_screen_observer()->HideErrorScreen(this);
492}
493
494void UpdateScreen::UpdateErrorMessage(
495    const NetworkState* network,
496    const NetworkPortalDetector::CaptivePortalStatus status) {
497  switch (status) {
498    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
499      NOTREACHED();
500      break;
501    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
502    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
503      GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_OFFLINE,
504                                      std::string());
505      break;
506    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
507      DCHECK(network);
508      GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PORTAL,
509                                      network->name());
510      if (is_first_portal_notification_) {
511        is_first_portal_notification_ = false;
512        GetErrorScreen()->FixCaptivePortal();
513      }
514      break;
515    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
516      GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PROXY,
517                                      std::string());
518      break;
519    default:
520      NOTREACHED();
521      break;
522  }
523}
524
525}  // namespace chromeos
526