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
GetInstanceSet()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
HasInstance(UpdateScreen * inst)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
UpdateScreen(WizardScreenDelegate * delegate)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
~UpdateScreen()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
UpdateStatusChanged(UpdateLibrary * library)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.
StartUpdateCallback(void * user_data,UpdateResult result,const char * msg)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
StartUpdate()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
CancelUpdate()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
Show()199 void UpdateScreen::Show() {
200 DefaultViewScreen<UpdateView>::Show();
201 view()->set_controller(this);
202 is_downloading_update_ = false;
203 view()->SetProgress(kBeforeUpdateCheckProgress);
204 }
205
ExitUpdate(UpdateScreen::ExitReason reason)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
OnWaitForRebootTimeElapsed()250 void UpdateScreen::OnWaitForRebootTimeElapsed() {
251 LOG(ERROR) << "Unable to reboot - asking user for a manual reboot.";
252 MakeSureScreenIsShown();
253 view()->ShowManualRebootInfo();
254 }
255
MakeSureScreenIsShown()256 void UpdateScreen::MakeSureScreenIsShown() {
257 if (!view()) {
258 delegate()->ShowCurrentScreen();
259 }
260 }
261
SetRebootCheckDelay(int seconds)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
HasCriticalUpdate()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
SetAllUpdatesCritical(bool is_critical)288 void UpdateScreen::SetAllUpdatesCritical(bool is_critical) {
289 is_all_updates_critical_ = is_critical;
290 }
291
292 } // namespace chromeos
293