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