1 // Copyright (c) 2011 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/update_screen.h" 6 7 #include "base/file_util.h" 8 #include "base/logging.h" 9 #include "base/threading/thread_restrictions.h" 10 #include "chrome/browser/chromeos/cros/cros_library.h" 11 #include "chrome/browser/chromeos/login/screen_observer.h" 12 #include "chrome/browser/chromeos/login/update_view.h" 13 #include "chrome/browser/chromeos/login/wizard_controller.h" 14 #include "content/browser/browser_thread.h" 15 16 namespace { 17 18 // Progress bar stages. Each represents progress bar value 19 // at the beginning of each stage. 20 // TODO(nkostylev): Base stage progress values on approximate time. 21 // TODO(nkostylev): Animate progress during each state. 22 const int kBeforeUpdateCheckProgress = 7; 23 const int kBeforeDownloadProgress = 14; 24 const int kBeforeVerifyingProgress = 74; 25 const int kBeforeFinalizingProgress = 81; 26 const int kProgressComplete = 100; 27 28 // Defines what part of update progress does download part takes. 29 const int kDownloadProgressIncrement = 60; 30 31 // Considering 10px shadow from each side. 32 const int kUpdateScreenWidth = 580; 33 const int kUpdateScreenHeight = 305; 34 35 const char kUpdateDeadlineFile[] = "/tmp/update-check-response-deadline"; 36 37 } // anonymous namespace 38 39 namespace chromeos { 40 41 42 // static 43 UpdateScreen::InstanceSet& UpdateScreen::GetInstanceSet() { 44 static std::set<UpdateScreen*> instance_set; 45 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // not threadsafe. 46 return instance_set; 47 } 48 49 // static 50 bool UpdateScreen::HasInstance(UpdateScreen* inst) { 51 InstanceSet& instance_set = GetInstanceSet(); 52 InstanceSet::iterator found = instance_set.find(inst); 53 return (found != instance_set.end()); 54 } 55 56 UpdateScreen::UpdateScreen(WizardScreenDelegate* delegate) 57 : DefaultViewScreen<chromeos::UpdateView>(delegate, 58 kUpdateScreenWidth, 59 kUpdateScreenHeight), 60 checking_for_update_(true), 61 reboot_check_delay_(0), 62 is_downloading_update_(false), 63 is_all_updates_critical_(true) { // See http://crosbug.com/10068 64 GetInstanceSet().insert(this); 65 } 66 67 UpdateScreen::~UpdateScreen() { 68 // Remove pointer to this object from view. 69 if (view()) 70 view()->set_controller(NULL); 71 CrosLibrary::Get()->GetUpdateLibrary()->RemoveObserver(this); 72 GetInstanceSet().erase(this); 73 } 74 75 void UpdateScreen::UpdateStatusChanged(UpdateLibrary* library) { 76 UpdateStatusOperation status = library->status().status; 77 if (checking_for_update_ && status > UPDATE_STATUS_CHECKING_FOR_UPDATE) { 78 checking_for_update_ = false; 79 } 80 81 switch (status) { 82 case UPDATE_STATUS_CHECKING_FOR_UPDATE: 83 // Do nothing in these cases, we don't want to notify the user of the 84 // check unless there is an update. 85 break; 86 case UPDATE_STATUS_UPDATE_AVAILABLE: 87 MakeSureScreenIsShown(); 88 view()->SetProgress(kBeforeDownloadProgress); 89 if (!HasCriticalUpdate()) { 90 LOG(INFO) << "Noncritical update available: " 91 << library->status().new_version; 92 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 93 } else { 94 LOG(INFO) << "Critical update available: " 95 << library->status().new_version; 96 view()->ShowPreparingUpdatesInfo(true); 97 view()->ShowCurtain(false); 98 } 99 break; 100 case UPDATE_STATUS_DOWNLOADING: 101 { 102 MakeSureScreenIsShown(); 103 if (!is_downloading_update_) { 104 // Because update engine doesn't send UPDATE_STATUS_UPDATE_AVAILABLE 105 // we need to is update critical on first downloading notification. 106 is_downloading_update_ = true; 107 if (!HasCriticalUpdate()) { 108 LOG(INFO) << "Non-critical update available: " 109 << library->status().new_version; 110 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 111 } else { 112 LOG(INFO) << "Critical update available: " 113 << library->status().new_version; 114 } 115 } 116 view()->ShowPreparingUpdatesInfo(false); 117 view()->ShowCurtain(false); 118 int download_progress = static_cast<int>( 119 library->status().download_progress * kDownloadProgressIncrement); 120 view()->SetProgress(kBeforeDownloadProgress + download_progress); 121 } 122 break; 123 case UPDATE_STATUS_VERIFYING: 124 MakeSureScreenIsShown(); 125 view()->SetProgress(kBeforeVerifyingProgress); 126 break; 127 case UPDATE_STATUS_FINALIZING: 128 MakeSureScreenIsShown(); 129 view()->SetProgress(kBeforeFinalizingProgress); 130 break; 131 case UPDATE_STATUS_UPDATED_NEED_REBOOT: 132 MakeSureScreenIsShown(); 133 // Make sure that first OOBE stage won't be shown after reboot. 134 WizardController::MarkOobeCompleted(); 135 view()->SetProgress(kProgressComplete); 136 if (HasCriticalUpdate()) { 137 view()->ShowCurtain(false); 138 VLOG(1) << "Initiate reboot after update"; 139 CrosLibrary::Get()->GetUpdateLibrary()->RebootAfterUpdate(); 140 reboot_timer_.Start(base::TimeDelta::FromSeconds(reboot_check_delay_), 141 this, 142 &UpdateScreen::OnWaitForRebootTimeElapsed); 143 } else { 144 ExitUpdate(REASON_UPDATE_NON_CRITICAL); 145 } 146 break; 147 case UPDATE_STATUS_IDLE: 148 case UPDATE_STATUS_ERROR: 149 case UPDATE_STATUS_REPORTING_ERROR_EVENT: 150 ExitUpdate(REASON_UPDATE_ENDED); 151 break; 152 default: 153 NOTREACHED(); 154 break; 155 } 156 } 157 158 namespace { 159 // Invoked from call to RequestUpdateCheck upon completion of the DBus call. 160 void StartUpdateCallback(void* user_data, 161 UpdateResult result, 162 const char* msg) { 163 if (result != UPDATE_RESULT_SUCCESS) { 164 DCHECK(user_data); 165 UpdateScreen* screen = static_cast<UpdateScreen*>(user_data); 166 if (UpdateScreen::HasInstance(screen)) 167 screen->ExitUpdate(UpdateScreen::REASON_UPDATE_INIT_FAILED); 168 } 169 } 170 } // namespace 171 172 void UpdateScreen::StartUpdate() { 173 // Reset view if view was created. 174 if (view()) { 175 view()->Reset(); 176 view()->set_controller(this); 177 is_downloading_update_ = false; 178 view()->SetProgress(kBeforeUpdateCheckProgress); 179 } 180 181 if (!CrosLibrary::Get()->EnsureLoaded()) { 182 LOG(ERROR) << "Error loading CrosLibrary"; 183 ExitUpdate(REASON_UPDATE_INIT_FAILED); 184 } else { 185 CrosLibrary::Get()->GetUpdateLibrary()->AddObserver(this); 186 VLOG(1) << "Initiate update check"; 187 CrosLibrary::Get()->GetUpdateLibrary()->RequestUpdateCheck( 188 StartUpdateCallback, this); 189 } 190 } 191 192 void UpdateScreen::CancelUpdate() { 193 // Screen has longer lifetime than it's view. 194 // View is deleted after wizard proceeds to the next screen. 195 if (view()) 196 ExitUpdate(REASON_UPDATE_CANCELED); 197 } 198 199 void UpdateScreen::Show() { 200 DefaultViewScreen<UpdateView>::Show(); 201 view()->set_controller(this); 202 is_downloading_update_ = false; 203 view()->SetProgress(kBeforeUpdateCheckProgress); 204 } 205 206 void UpdateScreen::ExitUpdate(UpdateScreen::ExitReason reason) { 207 ScreenObserver* observer = delegate()->GetObserver(this); 208 if (CrosLibrary::Get()->EnsureLoaded()) 209 CrosLibrary::Get()->GetUpdateLibrary()->RemoveObserver(this); 210 211 switch(reason) { 212 case REASON_UPDATE_CANCELED: 213 observer->OnExit(ScreenObserver::UPDATE_NOUPDATE); 214 break; 215 case REASON_UPDATE_INIT_FAILED: 216 observer->OnExit(ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE); 217 break; 218 case REASON_UPDATE_NON_CRITICAL: 219 case REASON_UPDATE_ENDED: 220 { 221 UpdateLibrary* update_library = CrosLibrary::Get()->GetUpdateLibrary(); 222 switch (update_library->status().status) { 223 case UPDATE_STATUS_UPDATE_AVAILABLE: 224 case UPDATE_STATUS_UPDATED_NEED_REBOOT: 225 case UPDATE_STATUS_DOWNLOADING: 226 case UPDATE_STATUS_FINALIZING: 227 case UPDATE_STATUS_VERIFYING: 228 DCHECK(!HasCriticalUpdate()); 229 // Noncritical update, just exit screen as if there is no update. 230 // no break 231 case UPDATE_STATUS_IDLE: 232 observer->OnExit(ScreenObserver::UPDATE_NOUPDATE); 233 break; 234 case UPDATE_STATUS_ERROR: 235 case UPDATE_STATUS_REPORTING_ERROR_EVENT: 236 observer->OnExit(checking_for_update_ ? 237 ScreenObserver::UPDATE_ERROR_CHECKING_FOR_UPDATE : 238 ScreenObserver::UPDATE_ERROR_UPDATING); 239 break; 240 default: 241 NOTREACHED(); 242 } 243 } 244 break; 245 default: 246 NOTREACHED(); 247 } 248 } 249 250 void UpdateScreen::OnWaitForRebootTimeElapsed() { 251 LOG(ERROR) << "Unable to reboot - asking user for a manual reboot."; 252 MakeSureScreenIsShown(); 253 view()->ShowManualRebootInfo(); 254 } 255 256 void UpdateScreen::MakeSureScreenIsShown() { 257 if (!view()) { 258 delegate()->ShowCurrentScreen(); 259 } 260 } 261 262 void UpdateScreen::SetRebootCheckDelay(int seconds) { 263 if (seconds <= 0) 264 reboot_timer_.Stop(); 265 DCHECK(!reboot_timer_.IsRunning()); 266 reboot_check_delay_ = seconds; 267 } 268 269 bool UpdateScreen::HasCriticalUpdate() { 270 if (is_all_updates_critical_) 271 return true; 272 273 std::string deadline; 274 // Checking for update flag file causes us to do blocking IO on UI thread. 275 // Temporarily allow it until we fix http://crosbug.com/11106 276 base::ThreadRestrictions::ScopedAllowIO allow_io; 277 FilePath update_deadline_file_path(kUpdateDeadlineFile); 278 if (!file_util::ReadFileToString(update_deadline_file_path, &deadline) || 279 deadline.empty()) { 280 return false; 281 } 282 283 // TODO(dpolukhin): Analyze file content. Now we can just assume that 284 // if the file exists and not empty, there is critical update. 285 return true; 286 } 287 288 void UpdateScreen::SetAllUpdatesCritical(bool is_critical) { 289 is_all_updates_critical_ = is_critical; 290 } 291 292 } // namespace chromeos 293