OLD | NEW |
| (Empty) |
1 // Copyright 2013 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/app_mode/startup_app_launcher.h" | |
6 | |
7 #include "ash/shell.h" | |
8 #include "base/command_line.h" | |
9 #include "base/files/file_path.h" | |
10 #include "base/json/json_file_value_serializer.h" | |
11 #include "base/path_service.h" | |
12 #include "base/time/time.h" | |
13 #include "base/values.h" | |
14 #include "chrome/browser/chrome_notification_types.h" | |
15 #include "chrome/browser/chromeos/app_mode/app_session_lifetime.h" | |
16 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" | |
17 #include "chrome/browser/chromeos/login/user_manager.h" | |
18 #include "chrome/browser/chromeos/ui/app_launch_view.h" | |
19 #include "chrome/browser/extensions/extension_service.h" | |
20 #include "chrome/browser/extensions/extension_system.h" | |
21 #include "chrome/browser/extensions/webstore_startup_installer.h" | |
22 #include "chrome/browser/lifetime/application_lifetime.h" | |
23 #include "chrome/browser/signin/profile_oauth2_token_service.h" | |
24 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" | |
25 #include "chrome/browser/signin/token_service.h" | |
26 #include "chrome/browser/signin/token_service_factory.h" | |
27 #include "chrome/browser/ui/extensions/application_launch.h" | |
28 #include "chrome/common/chrome_paths.h" | |
29 #include "chrome/common/chrome_switches.h" | |
30 #include "chrome/common/extensions/extension.h" | |
31 #include "chrome/common/extensions/manifest_handlers/kiosk_enabled_info.h" | |
32 #include "content/public/browser/browser_thread.h" | |
33 #include "content/public/browser/notification_service.h" | |
34 #include "google_apis/gaia/gaia_auth_consumer.h" | |
35 #include "google_apis/gaia/gaia_constants.h" | |
36 | |
37 using content::BrowserThread; | |
38 using extensions::Extension; | |
39 using extensions::WebstoreStartupInstaller; | |
40 | |
41 namespace chromeos { | |
42 | |
43 namespace { | |
44 | |
45 const char kOAuthRefreshToken[] = "refresh_token"; | |
46 const char kOAuthClientId[] = "client_id"; | |
47 const char kOAuthClientSecret[] = "client_secret"; | |
48 | |
49 const base::FilePath::CharType kOAuthFileName[] = | |
50 FILE_PATH_LITERAL("kiosk_auth"); | |
51 | |
52 // Application install splash screen minimum show time in milliseconds. | |
53 const int kAppInstallSplashScreenMinTimeMS = 3000; | |
54 | |
55 bool IsAppInstalled(Profile* profile, const std::string& app_id) { | |
56 return extensions::ExtensionSystem::Get(profile)->extension_service()-> | |
57 GetInstalledExtension(app_id); | |
58 } | |
59 | |
60 } // namespace | |
61 | |
62 StartupAppLauncher::StartupAppLauncher(Profile* profile, | |
63 const std::string& app_id) | |
64 : profile_(profile), | |
65 app_id_(app_id), | |
66 launch_splash_start_time_(0) { | |
67 DCHECK(profile_); | |
68 DCHECK(Extension::IdIsValid(app_id_)); | |
69 DCHECK(ash::Shell::HasInstance()); | |
70 ash::Shell::GetInstance()->AddPreTargetHandler(this); | |
71 } | |
72 | |
73 StartupAppLauncher::~StartupAppLauncher() { | |
74 DCHECK(ash::Shell::HasInstance()); | |
75 ash::Shell::GetInstance()->RemovePreTargetHandler(this); | |
76 } | |
77 | |
78 void StartupAppLauncher::Start() { | |
79 launch_splash_start_time_ = base::TimeTicks::Now().ToInternalValue(); | |
80 DVLOG(1) << "Starting... connection = " | |
81 << net::NetworkChangeNotifier::GetConnectionType(); | |
82 chromeos::ShowAppLaunchSplashScreen(app_id_); | |
83 StartLoadingOAuthFile(); | |
84 } | |
85 | |
86 void StartupAppLauncher::StartLoadingOAuthFile() { | |
87 KioskOAuthParams* auth_params = new KioskOAuthParams(); | |
88 BrowserThread::PostBlockingPoolTaskAndReply( | |
89 FROM_HERE, | |
90 base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool, | |
91 auth_params), | |
92 base::Bind(&StartupAppLauncher::OnOAuthFileLoaded, | |
93 AsWeakPtr(), | |
94 base::Owned(auth_params))); | |
95 } | |
96 | |
97 // static. | |
98 void StartupAppLauncher::LoadOAuthFileOnBlockingPool( | |
99 KioskOAuthParams* auth_params) { | |
100 int error_code = JSONFileValueSerializer::JSON_NO_ERROR; | |
101 std::string error_msg; | |
102 base::FilePath user_data_dir; | |
103 CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)); | |
104 base::FilePath auth_file = user_data_dir.Append(kOAuthFileName); | |
105 scoped_ptr<JSONFileValueSerializer> serializer( | |
106 new JSONFileValueSerializer(user_data_dir.Append(kOAuthFileName))); | |
107 scoped_ptr<base::Value> value( | |
108 serializer->Deserialize(&error_code, &error_msg)); | |
109 base::DictionaryValue* dict = NULL; | |
110 if (error_code != JSONFileValueSerializer::JSON_NO_ERROR || | |
111 !value.get() || !value->GetAsDictionary(&dict)) { | |
112 LOG(WARNING) << "Can't find auth file at " << auth_file.value(); | |
113 return; | |
114 } | |
115 | |
116 dict->GetString(kOAuthRefreshToken, &auth_params->refresh_token); | |
117 dict->GetString(kOAuthClientId, &auth_params->client_id); | |
118 dict->GetString(kOAuthClientSecret, &auth_params->client_secret); | |
119 } | |
120 | |
121 void StartupAppLauncher::OnOAuthFileLoaded(KioskOAuthParams* auth_params) { | |
122 auth_params_ = *auth_params; | |
123 // Override chrome client_id and secret that will be used for identity | |
124 // API token minting. | |
125 if (!auth_params_.client_id.empty() && !auth_params_.client_secret.empty()) { | |
126 UserManager::Get()->SetAppModeChromeClientOAuthInfo( | |
127 auth_params_.client_id, | |
128 auth_params_.client_secret); | |
129 } | |
130 | |
131 // If we are restarting chrome (i.e. on crash), we need to initialize | |
132 // TokenService as well. | |
133 InitializeTokenService(); | |
134 } | |
135 | |
136 void StartupAppLauncher::InitializeNetwork() { | |
137 chromeos::UpdateAppLaunchSplashScreenState( | |
138 chromeos::APP_LAUNCH_STATE_PREPARING_NETWORK); | |
139 // Set a maximum allowed wait time for network. | |
140 const int kMaxNetworkWaitSeconds = 5 * 60; | |
141 network_wait_timer_.Start( | |
142 FROM_HERE, | |
143 base::TimeDelta::FromSeconds(kMaxNetworkWaitSeconds), | |
144 this, &StartupAppLauncher::OnNetworkWaitTimedout); | |
145 | |
146 net::NetworkChangeNotifier::AddNetworkChangeObserver(this); | |
147 OnNetworkChanged(net::NetworkChangeNotifier::GetConnectionType()); | |
148 } | |
149 | |
150 void StartupAppLauncher::InitializeTokenService() { | |
151 chromeos::UpdateAppLaunchSplashScreenState( | |
152 chromeos::APP_LAUNCH_STATE_LOADING_TOKEN_SERVICE); | |
153 ProfileOAuth2TokenService* profile_token_service = | |
154 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); | |
155 if (profile_token_service->RefreshTokenIsAvailable()) { | |
156 InitializeNetwork(); | |
157 return; | |
158 } | |
159 | |
160 // At the end of this method, the execution will be put on hold until | |
161 // ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or | |
162 // OnRefreshTokensLoaded. Given that we want to handle exactly one event, | |
163 // whichever comes first, both handlers call RemoveObserver on PO2TS. Handling | |
164 // any of the two events is the only way to resume the execution and enable | |
165 // Cleanup method to be called, self-invoking a destructor. In destructor | |
166 // StartupAppLauncher is no longer an observer of PO2TS and there is no need | |
167 // to call RemoveObserver again. | |
168 profile_token_service->AddObserver(this); | |
169 | |
170 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); | |
171 token_service->Initialize(GaiaConstants::kChromeSource, profile_); | |
172 | |
173 // Pass oauth2 refresh token from the auth file. | |
174 // TODO(zelidrag): We should probably remove this option after M27. | |
175 // TODO(fgorski): This can go when we have persistence implemented on PO2TS. | |
176 // Unless the code is no longer needed. | |
177 if (!auth_params_.refresh_token.empty()) { | |
178 token_service->UpdateCredentialsWithOAuth2( | |
179 GaiaAuthConsumer::ClientOAuthResult( | |
180 auth_params_.refresh_token, | |
181 std::string(), // access_token | |
182 0)); // new_expires_in_secs | |
183 } else { | |
184 // Load whatever tokens we have stored there last time around. | |
185 token_service->LoadTokensFromDB(); | |
186 } | |
187 } | |
188 | |
189 void StartupAppLauncher::OnRefreshTokenAvailable( | |
190 const std::string& account_id) { | |
191 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_) | |
192 ->RemoveObserver(this); | |
193 InitializeNetwork(); | |
194 } | |
195 | |
196 void StartupAppLauncher::OnRefreshTokensLoaded() { | |
197 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_) | |
198 ->RemoveObserver(this); | |
199 InitializeNetwork(); | |
200 } | |
201 | |
202 void StartupAppLauncher::Cleanup() { | |
203 chromeos::CloseAppLaunchSplashScreen(); | |
204 | |
205 delete this; | |
206 } | |
207 | |
208 void StartupAppLauncher::OnLaunchSuccess() { | |
209 const int64 time_taken_ms = (base::TimeTicks::Now() - | |
210 base::TimeTicks::FromInternalValue(launch_splash_start_time_)). | |
211 InMilliseconds(); | |
212 | |
213 // Enforce that we show app install splash screen for some minimum amount | |
214 // of time. | |
215 if (time_taken_ms < kAppInstallSplashScreenMinTimeMS) { | |
216 BrowserThread::PostDelayedTask( | |
217 BrowserThread::UI, | |
218 FROM_HERE, | |
219 base::Bind(&StartupAppLauncher::OnLaunchSuccess, AsWeakPtr()), | |
220 base::TimeDelta::FromMilliseconds( | |
221 kAppInstallSplashScreenMinTimeMS - time_taken_ms)); | |
222 return; | |
223 } | |
224 | |
225 Cleanup(); | |
226 } | |
227 | |
228 void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) { | |
229 DCHECK_NE(KioskAppLaunchError::NONE, error); | |
230 | |
231 // Saves the error and ends the session to go back to login screen. | |
232 KioskAppLaunchError::Save(error); | |
233 chrome::AttemptUserExit(); | |
234 | |
235 Cleanup(); | |
236 } | |
237 | |
238 void StartupAppLauncher::Launch() { | |
239 const Extension* extension = extensions::ExtensionSystem::Get(profile_)-> | |
240 extension_service()->GetInstalledExtension(app_id_); | |
241 CHECK(extension); | |
242 | |
243 if (!extensions::KioskEnabledInfo::IsKioskEnabled(extension)) { | |
244 OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED); | |
245 return; | |
246 } | |
247 | |
248 // Always open the app in a window. | |
249 chrome::OpenApplication(chrome::AppLaunchParams(profile_, | |
250 extension, | |
251 extension_misc::LAUNCH_WINDOW, | |
252 NEW_WINDOW)); | |
253 InitAppSession(profile_, app_id_); | |
254 | |
255 content::NotificationService::current()->Notify( | |
256 chrome::NOTIFICATION_KIOSK_APP_LAUNCHED, | |
257 content::NotificationService::AllSources(), | |
258 content::NotificationService::NoDetails()); | |
259 | |
260 OnLaunchSuccess(); | |
261 } | |
262 | |
263 void StartupAppLauncher::BeginInstall() { | |
264 DVLOG(1) << "BeginInstall... connection = " | |
265 << net::NetworkChangeNotifier::GetConnectionType(); | |
266 | |
267 chromeos::UpdateAppLaunchSplashScreenState( | |
268 chromeos::APP_LAUNCH_STATE_INSTALLING_APPLICATION); | |
269 | |
270 if (IsAppInstalled(profile_, app_id_)) { | |
271 Launch(); | |
272 return; | |
273 } | |
274 | |
275 installer_ = new WebstoreStartupInstaller( | |
276 app_id_, | |
277 profile_, | |
278 false, | |
279 base::Bind(&StartupAppLauncher::InstallCallback, AsWeakPtr())); | |
280 installer_->BeginInstall(); | |
281 } | |
282 | |
283 void StartupAppLauncher::InstallCallback(bool success, | |
284 const std::string& error) { | |
285 installer_ = NULL; | |
286 if (success) { | |
287 // Schedules Launch() to be called after the callback returns. | |
288 // So that the app finishes its installation. | |
289 BrowserThread::PostTask( | |
290 BrowserThread::UI, | |
291 FROM_HERE, | |
292 base::Bind(&StartupAppLauncher::Launch, AsWeakPtr())); | |
293 return; | |
294 } | |
295 | |
296 LOG(ERROR) << "Failed to install app with error: " << error; | |
297 OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL); | |
298 } | |
299 | |
300 void StartupAppLauncher::OnNetworkWaitTimedout() { | |
301 LOG(WARNING) << "OnNetworkWaitTimedout... connection = " | |
302 << net::NetworkChangeNotifier::GetConnectionType(); | |
303 // Timeout in waiting for online. Try the install anyway. | |
304 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); | |
305 BeginInstall(); | |
306 } | |
307 | |
308 void StartupAppLauncher::OnNetworkChanged( | |
309 net::NetworkChangeNotifier::ConnectionType type) { | |
310 DVLOG(1) << "OnNetworkChanged... connection = " | |
311 << net::NetworkChangeNotifier::GetConnectionType(); | |
312 if (!net::NetworkChangeNotifier::IsOffline()) { | |
313 DVLOG(1) << "Network up and running!"; | |
314 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); | |
315 network_wait_timer_.Stop(); | |
316 | |
317 BeginInstall(); | |
318 } else { | |
319 DVLOG(1) << "Network not running yet!"; | |
320 } | |
321 } | |
322 | |
323 void StartupAppLauncher::OnKeyEvent(ui::KeyEvent* event) { | |
324 if (event->type() != ui::ET_KEY_PRESSED) | |
325 return; | |
326 | |
327 if (KioskAppManager::Get()->GetDisableBailoutShortcut()) | |
328 return; | |
329 | |
330 if (event->key_code() != ui::VKEY_S || | |
331 !(event->flags() & ui::EF_CONTROL_DOWN) || | |
332 !(event->flags() & ui::EF_ALT_DOWN)) { | |
333 return; | |
334 } | |
335 | |
336 OnLaunchFailure(KioskAppLaunchError::USER_CANCEL); | |
337 } | |
338 | |
339 } // namespace chromeos | |
OLD | NEW |