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/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 
     24 using content::BrowserThread;
     25 
     26 namespace chromeos {
     27 
     28 namespace {
     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.
     34 const int kBeforeUpdateCheckProgress = 7;
     35 const int kBeforeDownloadProgress = 14;
     36 const int kBeforeVerifyingProgress = 74;
     37 const int kBeforeFinalizingProgress = 81;
     38 const int kProgressComplete = 100;
     39 
     40 // Defines what part of update progress does download part takes.
     41 const int kDownloadProgressIncrement = 60;
     42 
     43 const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline";
     44 
     45 // Minimum timestep between two consecutive measurements for the
     46 // download rate.
     47 const 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.
     52 const double kDownloadSpeedSmoothFactor = 0.1;
     53 
     54 // Minumum allowed value for the average downloading speed.
     55 const double kDownloadAverageSpeedDropBound = 1e-8;
     56 
     57 // An upper bound for possible downloading time left estimations.
     58 const double kMaxTimeLeft = 24 * 60 * 60;
     59 
     60 // Invoked from call to RequestUpdateCheck upon completion of the DBus call.
     61 void 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
     75 UpdateScreen::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
     82 bool 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
     89 UpdateScreen* UpdateScreen::Get(ScreenManager* manager) {
     90   return static_cast<UpdateScreen*>(
     91       manager->GetScreen(WizardController::kUpdateScreenName));
     92 }
     93 
     94 UpdateScreen::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 
    115 UpdateScreen::~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 
    123 void 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 
    231 void 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 
    279 void 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 
    293 void UpdateScreen::CancelUpdate() {
    294   VLOG(1) << "Forced update cancel";
    295   ExitUpdate(REASON_UPDATE_CANCELED);
    296 }
    297 
    298 void UpdateScreen::Show() {
    299   is_shown_ = true;
    300   if (actor_) {
    301     actor_->Show();
    302     actor_->SetProgress(kBeforeUpdateCheckProgress);
    303   }
    304 }
    305 
    306 void UpdateScreen::Hide() {
    307   if (actor_)
    308     actor_->Hide();
    309   is_shown_ = false;
    310 }
    311 
    312 std::string UpdateScreen::GetName() const {
    313   return WizardController::kUpdateScreenName;
    314 }
    315 
    316 void UpdateScreen::PrepareToShow() {
    317   if (actor_)
    318     actor_->PrepareToShow();
    319 }
    320 
    321 void 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 
    368 void UpdateScreen::OnWaitForRebootTimeElapsed() {
    369   LOG(ERROR) << "Unable to reboot - asking user for a manual reboot.";
    370   MakeSureScreenIsShown();
    371   if (actor_)
    372     actor_->ShowManualRebootInfo();
    373 }
    374 
    375 void UpdateScreen::MakeSureScreenIsShown() {
    376   if (!is_shown_)
    377     get_screen_observer()->ShowCurrentScreen();
    378 }
    379 
    380 void UpdateScreen::SetRebootCheckDelay(int seconds) {
    381   if (seconds <= 0)
    382     reboot_timer_.Stop();
    383   DCHECK(!reboot_timer_.IsRunning());
    384   reboot_check_delay_ = seconds;
    385 }
    386 
    387 void UpdateScreen::SetIgnoreIdleStatus(bool ignore_idle_status) {
    388   ignore_idle_status_ = ignore_idle_status;
    389 }
    390 
    391 void 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 
    440 bool 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 
    459 void UpdateScreen::OnActorDestroyed(UpdateScreenActor* actor) {
    460   if (actor_ == actor)
    461     actor_ = NULL;
    462 }
    463 
    464 void UpdateScreen::OnConnectToNetworkRequested() {
    465   if (state_ == STATE_ERROR) {
    466     LOG(WARNING) << "Hiding error message since AP was reselected";
    467     StartUpdateCheck();
    468   }
    469 }
    470 
    471 ErrorScreen* UpdateScreen::GetErrorScreen() {
    472   return get_screen_observer()->GetErrorScreen();
    473 }
    474 
    475 void 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 
    486 void 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 
    493 void UpdateScreen::HideErrorMessage() {
    494   LOG(WARNING) << "UpdateScreen::HideErrorMessage()";
    495   get_screen_observer()->HideErrorScreen(this);
    496 }
    497 
    498 void 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