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