Home | History | Annotate | Download | only in screens
      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 
     25 using content::BrowserThread;
     26 
     27 namespace chromeos {
     28 
     29 namespace {
     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.
     35 const int kBeforeUpdateCheckProgress = 7;
     36 const int kBeforeDownloadProgress = 14;
     37 const int kBeforeVerifyingProgress = 74;
     38 const int kBeforeFinalizingProgress = 81;
     39 const int kProgressComplete = 100;
     40 
     41 // Defines what part of update progress does download part takes.
     42 const int kDownloadProgressIncrement = 60;
     43 
     44 const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline";
     45 
     46 // Minimum timestep between two consecutive measurements for the
     47 // download rate.
     48 const 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.
     53 const double kDownloadSpeedSmoothFactor = 0.1;
     54 
     55 // Minumum allowed value for the average downloading speed.
     56 const double kDownloadAverageSpeedDropBound = 1e-8;
     57 
     58 // An upper bound for possible downloading time left estimations.
     59 const double kMaxTimeLeft = 24 * 60 * 60;
     60 
     61 // Invoked from call to RequestUpdateCheck upon completion of the DBus call.
     62 void 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.
     74 bool IsBlockingUpdateEnabledInCommandLine() {
     75   return !CommandLine::ForCurrentProcess()->HasSwitch(
     76       chromeos::switches::kDisableOOBEBlockingUpdate);
     77 }
     78 
     79 }  // anonymous namespace
     80 
     81 // static
     82 UpdateScreen::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
     89 bool 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 
     95 UpdateScreen::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 
    116 UpdateScreen::~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 
    124 void 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 
    236 void 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 
    284 void 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 
    299 void UpdateScreen::CancelUpdate() {
    300   VLOG(1) << "Forced update cancel";
    301   ExitUpdate(REASON_UPDATE_CANCELED);
    302 }
    303 
    304 void UpdateScreen::Show() {
    305   is_shown_ = true;
    306   if (actor_) {
    307     actor_->Show();
    308     actor_->SetProgress(kBeforeUpdateCheckProgress);
    309   }
    310 }
    311 
    312 void UpdateScreen::Hide() {
    313   if (actor_)
    314     actor_->Hide();
    315   is_shown_ = false;
    316 }
    317 
    318 std::string UpdateScreen::GetName() const {
    319   return WizardController::kUpdateScreenName;
    320 }
    321 
    322 void UpdateScreen::PrepareToShow() {
    323   if (actor_)
    324     actor_->PrepareToShow();
    325 }
    326 
    327 void 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 
    372 void UpdateScreen::OnWaitForRebootTimeElapsed() {
    373   LOG(ERROR) << "Unable to reboot - asking user for a manual reboot.";
    374   MakeSureScreenIsShown();
    375   if (actor_)
    376     actor_->ShowManualRebootInfo();
    377 }
    378 
    379 void UpdateScreen::MakeSureScreenIsShown() {
    380   if (!is_shown_)
    381     get_screen_observer()->ShowCurrentScreen();
    382 }
    383 
    384 void UpdateScreen::SetRebootCheckDelay(int seconds) {
    385   if (seconds <= 0)
    386     reboot_timer_.Stop();
    387   DCHECK(!reboot_timer_.IsRunning());
    388   reboot_check_delay_ = seconds;
    389 }
    390 
    391 void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status) {
    392   ignore_idle_status_ = ignore_idle_status;
    393 }
    394 
    395 void 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 
    444 bool 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 
    463 void UpdateScreen::OnActorDestroyed(UpdateScreenActor* actor) {
    464   if (actor_ == actor)
    465     actor_ = NULL;
    466 }
    467 
    468 void 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 
    476 ErrorScreen* UpdateScreen::GetErrorScreen() {
    477   return get_screen_observer()->GetErrorScreen();
    478 }
    479 
    480 void 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 
    491 void 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 
    498 void UpdateScreen::HideErrorMessage() {
    499   LOG(WARNING) << "UpdateScreen::HideErrorMessage()";
    500   get_screen_observer()->HideErrorScreen(this);
    501 }
    502 
    503 void 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