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