| 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 |