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