update_screen.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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.h"
14#include "base/threading/thread_restrictions.h"
15#include "chrome/browser/chromeos/cros/network_library.h"
16#include "chrome/browser/chromeos/login/screens/error_screen.h"
17#include "chrome/browser/chromeos/login/screens/screen_observer.h"
18#include "chrome/browser/chromeos/login/screens/update_screen_actor.h"
19#include "chrome/browser/chromeos/login/startup_utils.h"
20#include "chrome/browser/chromeos/login/wizard_controller.h"
21#include "chromeos/chromeos_switches.h"
22#include "chromeos/dbus/dbus_thread_manager.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_portal_notification_(true),
115      weak_factory_(this) {
116  DCHECK(actor_);
117  if (actor_)
118    actor_->SetDelegate(this);
119  GetInstanceSet().insert(this);
120}
121
122UpdateScreen::~UpdateScreen() {
123  DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
124  GetInstanceSet().erase(this);
125  if (actor_)
126    actor_->SetDelegate(NULL);
127}
128
129void UpdateScreen::UpdateStatusChanged(
130    const UpdateEngineClient::Status& status) {
131  if (!actor_)
132    return;
133
134  if (is_checking_for_update_ &&
135      status.status > UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE) {
136    is_checking_for_update_ = false;
137  }
138  if (ignore_idle_status_ && status.status >
139      UpdateEngineClient::UPDATE_STATUS_IDLE) {
140    ignore_idle_status_ = false;
141  }
142
143  switch (status.status) {
144    case UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE:
145      // Do nothing in these cases, we don't want to notify the user of the
146      // check unless there is an update.
147      break;
148    case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE:
149      MakeSureScreenIsShown();
150      actor_->SetProgress(kBeforeDownloadProgress);
151      actor_->ShowEstimatedTimeLeft(false);
152      if (!HasCriticalUpdate()) {
153        LOG(INFO) << "Noncritical update available: "
154                  << status.new_version;
155        ExitUpdate(REASON_UPDATE_NON_CRITICAL);
156      } else {
157        LOG(INFO) << "Critical update available: "
158                  << status.new_version;
159        actor_->SetProgressMessage(
160            UpdateScreenActor::PROGRESS_MESSAGE_UPDATE_AVAILABLE);
161        actor_->ShowProgressMessage(true);
162        actor_->ShowCurtain(false);
163      }
164      break;
165    case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING:
166      {
167        MakeSureScreenIsShown();
168        if (!is_downloading_update_) {
169          // Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE
170          // we need to is update critical on first downloading notification.
171          is_downloading_update_ = true;
172          download_start_time_ = download_last_time_ = base::Time::Now();
173          download_start_progress_ = status.download_progress;
174          download_last_progress_ = status.download_progress;
175          is_download_average_speed_computed_ = false;
176          download_average_speed_ = 0.0;
177          if (!HasCriticalUpdate()) {
178            LOG(INFO) << "Non-critical update available: "
179                      << status.new_version;
180            ExitUpdate(REASON_UPDATE_NON_CRITICAL);
181          } else {
182            LOG(INFO) << "Critical update available: "
183                      << status.new_version;
184            actor_->SetProgressMessage(
185                UpdateScreenActor::PROGRESS_MESSAGE_INSTALLING_UPDATE);
186            actor_->ShowProgressMessage(true);
187            actor_->ShowCurtain(false);
188          }
189        }
190        UpdateDownloadingStats(status);
191      }
192      break;
193    case UpdateEngineClient::UPDATE_STATUS_VERIFYING:
194      MakeSureScreenIsShown();
195      actor_->SetProgress(kBeforeVerifyingProgress);
196      actor_->SetProgressMessage(UpdateScreenActor::PROGRESS_MESSAGE_VERIFYING);
197      actor_->ShowProgressMessage(true);
198      break;
199    case UpdateEngineClient::UPDATE_STATUS_FINALIZING:
200      MakeSureScreenIsShown();
201      actor_->SetProgress(kBeforeFinalizingProgress);
202      actor_->SetProgressMessage(
203          UpdateScreenActor::PROGRESS_MESSAGE_FINALIZING);
204      actor_->ShowProgressMessage(true);
205      break;
206    case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT:
207      MakeSureScreenIsShown();
208      // Make sure that first OOBE stage won't be shown after reboot.
209      StartupUtils::MarkOobeCompleted();
210      actor_->SetProgress(kProgressComplete);
211      actor_->ShowEstimatedTimeLeft(false);
212      if (HasCriticalUpdate()) {
213        actor_->ShowCurtain(false);
214        VLOG(1) << "Initiate reboot after update";
215        DBusThreadManager::Get()->GetUpdateEngineClient()->RebootAfterUpdate();
216        reboot_timer_.Start(FROM_HERE,
217                            base::TimeDelta::FromSeconds(reboot_check_delay_),
218                            this,
219                            &UpdateScreen::OnWaitForRebootTimeElapsed);
220      } else {
221        ExitUpdate(REASON_UPDATE_NON_CRITICAL);
222      }
223      break;
224    case UpdateEngineClient::UPDATE_STATUS_IDLE:
225      if (ignore_idle_status_) {
226        // It is first IDLE status that is sent before we initiated the check.
227        break;
228      }
229      // else no break
230
231    case UpdateEngineClient::UPDATE_STATUS_ERROR:
232    case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT:
233      ExitUpdate(REASON_UPDATE_ENDED);
234      break;
235    default:
236      NOTREACHED();
237      break;
238  }
239}
240
241void UpdateScreen::OnPortalDetectionCompleted(
242    const Network* network,
243    const NetworkPortalDetector::CaptivePortalState& state) {
244  // Wait for the sane portal detection results.
245  if (!network ||
246      state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN) {
247    return;
248  }
249  LOG(WARNING) << "UpdateScreen::OnPortalDetectionCompleted(): "
250               << "network=" << network->service_path() << ", "
251               << "state.status=" << state.status << ", "
252               << "state.response_code=" << state.response_code;
253  NetworkPortalDetector::CaptivePortalStatus status = state.status;
254  if (state_ == STATE_ERROR) {
255    // In the case of online state hide error message and proceed to
256    // the update stage. Otherwise, update error message content.
257    if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) {
258      HideErrorMessage();
259      StartUpdateCheck();
260    } else {
261      UpdateErrorMessage(network, status);
262    }
263  } else if (state_ == STATE_FIRST_PORTAL_CHECK) {
264    // In the case of online state immediately proceed to the update
265    // stage. Otherwise, prepare and show error message.
266    if (status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE) {
267      StartUpdateCheck();
268    } else {
269      UpdateErrorMessage(network, status);
270      ShowErrorMessage();
271    }
272  }
273}
274
275void UpdateScreen::StartNetworkCheck() {
276  NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance();
277
278  // If portal detector is enabled and portal detection before AU is
279  // allowed, initiate network state check. Otherwise, directly
280  // proceed to update.
281  if (!NetworkPortalDetector::IsEnabledInCommandLine() || !detector ||
282      !IsBlockingUpdateEnabledInCommandLine()) {
283    StartUpdateCheck();
284    return;
285  }
286  state_ = STATE_FIRST_PORTAL_CHECK;
287  detector->AddAndFireObserver(this);
288}
289
290void UpdateScreen::CancelUpdate() {
291  VLOG(1) << "Forced update cancel";
292  ExitUpdate(REASON_UPDATE_CANCELED);
293}
294
295void UpdateScreen::Show() {
296  is_shown_ = true;
297  if (actor_) {
298    actor_->Show();
299    actor_->SetProgress(kBeforeUpdateCheckProgress);
300  }
301}
302
303void UpdateScreen::Hide() {
304  if (actor_)
305    actor_->Hide();
306  is_shown_ = false;
307}
308
309std::string UpdateScreen::GetName() const {
310  return WizardController::kUpdateScreenName;
311}
312
313void UpdateScreen::PrepareToShow() {
314  if (actor_)
315    actor_->PrepareToShow();
316}
317
318void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) {
319  DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
320  if (NetworkPortalDetector::GetInstance())
321    NetworkPortalDetector::GetInstance()->RemoveObserver(this);
322
323  switch (reason) {
324    case REASON_UPDATE_CANCELED:
325      get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE);
326      break;
327    case REASON_UPDATE_INIT_FAILED:
328      get_screen_observer()->OnExit(
329          ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE);
330      break;
331    case REASON_UPDATE_NON_CRITICAL:
332    case REASON_UPDATE_ENDED:
333      {
334        UpdateEngineClient* update_engine_client =
335            DBusThreadManager::Get()->GetUpdateEngineClient();
336        switch (update_engine_client->GetLastStatus().status) {
337          case UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE:
338          case UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT:
339          case UpdateEngineClient::UPDATE_STATUS_DOWNLOADING:
340          case UpdateEngineClient::UPDATE_STATUS_FINALIZING:
341          case UpdateEngineClient::UPDATE_STATUS_VERIFYING:
342            DCHECK(!HasCriticalUpdate());
343            // Noncritical update, just exit screen as if there is no update.
344            // no break
345          case UpdateEngineClient::UPDATE_STATUS_IDLE:
346            get_screen_observer()->OnExit(ScreenObserver::UPDATE_NOUPDATE);
347            break;
348          case UpdateEngineClient::UPDATE_STATUS_ERROR:
349          case UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT:
350            get_screen_observer()->OnExit(is_checking_for_update_ ?
351                ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE :
352                ScreenObserver::UPDATE_ERROR_UPDATING);
353            break;
354          default:
355            NOTREACHED();
356        }
357      }
358      break;
359    default:
360      NOTREACHED();
361  }
362}
363
364void UpdateScreen::OnWaitForRebootTimeElapsed() {
365  LOG(ERROR) << "Unable to reboot - asking user for a manual reboot.";
366  MakeSureScreenIsShown();
367  if (actor_)
368    actor_->ShowManualRebootInfo();
369}
370
371void UpdateScreen::MakeSureScreenIsShown() {
372  if (!is_shown_)
373    get_screen_observer()->ShowCurrentScreen();
374}
375
376void UpdateScreen::SetRebootCheckDelay(int seconds) {
377  if (seconds <= 0)
378    reboot_timer_.Stop();
379  DCHECK(!reboot_timer_.IsRunning());
380  reboot_check_delay_ = seconds;
381}
382
383void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status) {
384  ignore_idle_status_ = ignore_idle_status;
385}
386
387void UpdateScreen::UpdateDownloadingStats(
388    const UpdateEngineClient::Status& status) {
389  if (!actor_)
390    return;
391  base::Time download_current_time = base::Time::Now();
392  if (download_current_time >= download_last_time_ + kMinTimeStep) {
393    // Estimate downloading rate.
394    double progress_delta =
395        std::max(status.download_progress - download_last_progress_, 0.0);
396    double time_delta =
397        (download_current_time - download_last_time_).InSecondsF();
398    double download_rate = status.new_size * progress_delta / time_delta;
399
400    download_last_time_ = download_current_time;
401    download_last_progress_ = status.download_progress;
402
403    // Estimate time left.
404    double progress_left = std::max(1.0 - status.download_progress, 0.0);
405    if (!is_download_average_speed_computed_) {
406      download_average_speed_ = download_rate;
407      is_download_average_speed_computed_ = true;
408    }
409    download_average_speed_ =
410        kDownloadSpeedSmoothFactor * download_rate +
411        (1.0 - kDownloadSpeedSmoothFactor) * download_average_speed_;
412    if (download_average_speed_ < kDownloadAverageSpeedDropBound) {
413      time_delta =
414          (download_current_time - download_start_time_).InSecondsF();
415      download_average_speed_ =
416          status.new_size *
417          (status.download_progress - download_start_progress_) /
418          time_delta;
419    }
420    double work_left = progress_left * status.new_size;
421    double time_left = work_left / download_average_speed_;
422    // |time_left| may be large enough or even +infinity. So we must
423    // |bound possible estimations.
424    time_left = std::min(time_left, kMaxTimeLeft);
425
426    actor_->ShowEstimatedTimeLeft(true);
427    actor_->SetEstimatedTimeLeft(
428        base::TimeDelta::FromSeconds(static_cast<int64>(time_left)));
429  }
430
431  int download_progress = static_cast<int>(
432      status.download_progress * kDownloadProgressIncrement);
433  actor_->SetProgress(kBeforeDownloadProgress + download_progress);
434}
435
436bool UpdateScreen::HasCriticalUpdate() {
437  if (is_ignore_update_deadlines_)
438    return true;
439
440  std::string deadline;
441  // Checking for update flag file causes us to do blocking IO on UI thread.
442  // Temporarily allow it until we fix http://crosbug.com/11106
443  base::ThreadRestrictions::ScopedAllowIO allow_io;
444  base::FilePath update_deadline_file_path(kUpdateDeadlineFile);
445  if (!file_util::ReadFileToString(update_deadline_file_path, &deadline) ||
446      deadline.empty()) {
447    return false;
448  }
449
450  // TODO(dpolukhin): Analyze file content. Now we can just assume that
451  // if the file exists and not empty, there is critical update.
452  return true;
453}
454
455void UpdateScreen::OnActorDestroyed(UpdateScreenActor* actor) {
456  if (actor_ == actor)
457    actor_ = NULL;
458}
459
460ErrorScreen* UpdateScreen::GetErrorScreen() {
461  return get_screen_observer()->GetErrorScreen();
462}
463
464void UpdateScreen::StartUpdateCheck() {
465  NetworkPortalDetector* detector = NetworkPortalDetector::GetInstance();
466  if (detector)
467    detector->RemoveObserver(this);
468  state_ = STATE_UPDATE;
469  DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this);
470  VLOG(1) << "Initiate update check";
471  DBusThreadManager::Get()->GetUpdateEngineClient()->RequestUpdateCheck(
472      base::Bind(StartUpdateCallback, this));
473}
474
475void UpdateScreen::ShowErrorMessage() {
476  LOG(WARNING) << "UpdateScreen::ShowErrorMessage()";
477  state_ = STATE_ERROR;
478  GetErrorScreen()->SetUIState(ErrorScreen::UI_STATE_UPDATE);
479  get_screen_observer()->ShowErrorScreen();
480}
481
482void UpdateScreen::HideErrorMessage() {
483  LOG(WARNING) << "UpdateScreen::HideErrorMessage()";
484  get_screen_observer()->HideErrorScreen(this);
485}
486
487void UpdateScreen::UpdateErrorMessage(
488    const Network* network,
489    const NetworkPortalDetector::CaptivePortalStatus status) {
490  switch (status) {
491    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
492    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
493      NOTREACHED();
494      break;
495    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
496      GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_OFFLINE,
497                                      std::string());
498      break;
499    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
500      GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PORTAL,
501                                      network->name());
502      if (is_first_portal_notification_) {
503        is_first_portal_notification_ = false;
504        GetErrorScreen()->FixCaptivePortal();
505      }
506      break;
507    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
508      GetErrorScreen()->SetErrorState(ErrorScreen::ERROR_STATE_PROXY,
509                                      std::string());
510      break;
511    default:
512      NOTREACHED();
513      break;
514  }
515}
516
517}  // namespace chromeos
518