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