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.
5 #include "chrome/browser/chromeos/app_mode/startup_app_launcher.h"
7 #include "base/command_line.h"
8 #include "base/files/file_path.h"
9 #include "base/json/json_file_value_serializer.h"
10 #include "base/path_service.h"
11 #include "base/time/time.h"
12 #include "base/values.h"
13 #include "chrome/browser/chrome_notification_types.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/extensions/extension_service.h"
18 #include "chrome/browser/extensions/extension_system.h"
19 #include "chrome/browser/extensions/webstore_startup_installer.h"
20 #include "chrome/browser/lifetime/application_lifetime.h"
21 #include "chrome/browser/signin/profile_oauth2_token_service.h"
22 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
23 #include "chrome/browser/signin/token_service.h"
24 #include "chrome/browser/signin/token_service_factory.h"
25 #include "chrome/browser/ui/extensions/application_launch.h"
26 #include "chrome/common/chrome_paths.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/common/extensions/extension.h"
29 #include "chrome/common/extensions/manifest_handlers/kiosk_mode_info.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/notification_service.h"
32 #include "google_apis/gaia/gaia_auth_consumer.h"
33 #include "google_apis/gaia/gaia_constants.h"
35 using content::BrowserThread;
36 using extensions::Extension;
37 using extensions::WebstoreStartupInstaller;
43 const char kOAuthRefreshToken[] = "refresh_token";
44 const char kOAuthClientId[] = "client_id";
45 const char kOAuthClientSecret[] = "client_secret";
47 const base::FilePath::CharType kOAuthFileName[] =
48 FILE_PATH_LITERAL("kiosk_auth");
50 bool IsAppInstalled(Profile* profile, const std::string& app_id) {
51 return extensions::ExtensionSystem::Get(profile)->extension_service()->
52 GetInstalledExtension(app_id);
58 StartupAppLauncher::StartupAppLauncher(Profile* profile,
59 const std::string& app_id)
62 ready_to_launch_(false) {
64 DCHECK(Extension::IdIsValid(app_id_));
67 StartupAppLauncher::~StartupAppLauncher() {
68 // StartupAppLauncher can be deleted at anytime during the launch process
69 // through a user bailout shortcut.
70 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
71 ->RemoveObserver(this);
72 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
75 void StartupAppLauncher::Initialize() {
76 DVLOG(1) << "Starting... connection = "
77 << net::NetworkChangeNotifier::GetConnectionType();
78 StartLoadingOAuthFile();
81 void StartupAppLauncher::AddObserver(Observer* observer) {
82 observer_list_.AddObserver(observer);
85 void StartupAppLauncher::RemoveObserver(Observer* observer) {
86 observer_list_.RemoveObserver(observer);
89 void StartupAppLauncher::StartLoadingOAuthFile() {
90 FOR_EACH_OBSERVER(Observer, observer_list_, OnLoadingOAuthFile());
92 KioskOAuthParams* auth_params = new KioskOAuthParams();
93 BrowserThread::PostBlockingPoolTaskAndReply(
95 base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool,
97 base::Bind(&StartupAppLauncher::OnOAuthFileLoaded,
99 base::Owned(auth_params)));
103 void StartupAppLauncher::LoadOAuthFileOnBlockingPool(
104 KioskOAuthParams* auth_params) {
105 int error_code = JSONFileValueSerializer::JSON_NO_ERROR;
106 std::string error_msg;
107 base::FilePath user_data_dir;
108 CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
109 base::FilePath auth_file = user_data_dir.Append(kOAuthFileName);
110 scoped_ptr<JSONFileValueSerializer> serializer(
111 new JSONFileValueSerializer(user_data_dir.Append(kOAuthFileName)));
112 scoped_ptr<base::Value> value(
113 serializer->Deserialize(&error_code, &error_msg));
114 base::DictionaryValue* dict = NULL;
115 if (error_code != JSONFileValueSerializer::JSON_NO_ERROR ||
116 !value.get() || !value->GetAsDictionary(&dict)) {
117 LOG(WARNING) << "Can't find auth file at " << auth_file.value();
121 dict->GetString(kOAuthRefreshToken, &auth_params->refresh_token);
122 dict->GetString(kOAuthClientId, &auth_params->client_id);
123 dict->GetString(kOAuthClientSecret, &auth_params->client_secret);
126 void StartupAppLauncher::OnOAuthFileLoaded(KioskOAuthParams* auth_params) {
127 auth_params_ = *auth_params;
128 // Override chrome client_id and secret that will be used for identity
129 // API token minting.
130 if (!auth_params_.client_id.empty() && !auth_params_.client_secret.empty()) {
131 UserManager::Get()->SetAppModeChromeClientOAuthInfo(
132 auth_params_.client_id,
133 auth_params_.client_secret);
136 // If we are restarting chrome (i.e. on crash), we need to initialize
137 // TokenService as well.
138 InitializeTokenService();
141 void StartupAppLauncher::InitializeNetwork() {
142 FOR_EACH_OBSERVER(Observer, observer_list_, OnInitializingNetwork());
144 // TODO(tengs): Use NetworkStateInformer instead because it can handle
145 // portal and proxy detection. We will need to do some refactoring to
146 // make NetworkStateInformer more independent from the WebUI handlers.
147 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
148 OnNetworkChanged(net::NetworkChangeNotifier::GetConnectionType());
151 void StartupAppLauncher::InitializeTokenService() {
152 FOR_EACH_OBSERVER(Observer, observer_list_, OnInitializingTokenService());
154 ProfileOAuth2TokenService* profile_token_service =
155 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
156 if (profile_token_service->RefreshTokenIsAvailable(
157 profile_token_service->GetPrimaryAccountId())) {
162 // At the end of this method, the execution will be put on hold until
163 // ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or
164 // OnRefreshTokensLoaded. Given that we want to handle exactly one event,
165 // whichever comes first, both handlers call RemoveObserver on PO2TS. Handling
166 // any of the two events is the only way to resume the execution and enable
167 // Cleanup method to be called, self-invoking a destructor. In destructor
168 // StartupAppLauncher is no longer an observer of PO2TS and there is no need
169 // to call RemoveObserver again.
170 profile_token_service->AddObserver(this);
172 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_);
173 token_service->Initialize(GaiaConstants::kChromeSource, profile_);
175 // Pass oauth2 refresh token from the auth file.
176 // TODO(zelidrag): We should probably remove this option after M27.
177 // TODO(fgorski): This can go when we have persistence implemented on PO2TS.
178 // Unless the code is no longer needed.
179 if (!auth_params_.refresh_token.empty()) {
180 token_service->UpdateCredentialsWithOAuth2(
181 GaiaAuthConsumer::ClientOAuthResult(
182 auth_params_.refresh_token,
183 std::string(), // access_token
184 0)); // new_expires_in_secs
186 // Load whatever tokens we have stored there last time around.
187 token_service->LoadTokensFromDB();
191 void StartupAppLauncher::OnRefreshTokenAvailable(
192 const std::string& account_id) {
193 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
194 ->RemoveObserver(this);
198 void StartupAppLauncher::OnRefreshTokensLoaded() {
199 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
200 ->RemoveObserver(this);
204 void StartupAppLauncher::OnLaunchSuccess() {
205 FOR_EACH_OBSERVER(Observer, observer_list_, OnLaunchSucceeded());
208 void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) {
209 LOG(ERROR) << "App launch failed, error: " << error;
210 DCHECK_NE(KioskAppLaunchError::NONE, error);
212 FOR_EACH_OBSERVER(Observer, observer_list_, OnLaunchFailed(error));
215 void StartupAppLauncher::LaunchApp() {
216 if (!ready_to_launch_) {
218 LOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
221 const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
222 extension_service()->GetInstalledExtension(app_id_);
225 if (!extensions::KioskModeInfo::IsKioskEnabled(extension)) {
226 OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED);
230 // Always open the app in a window.
231 OpenApplication(AppLaunchParams(profile_, extension,
232 extension_misc::LAUNCH_WINDOW, NEW_WINDOW));
233 InitAppSession(profile_, app_id_);
235 UserManager::Get()->SessionStarted();
237 content::NotificationService::current()->Notify(
238 chrome::NOTIFICATION_KIOSK_APP_LAUNCHED,
239 content::NotificationService::AllSources(),
240 content::NotificationService::NoDetails());
245 void StartupAppLauncher::BeginInstall() {
246 FOR_EACH_OBSERVER(Observer, observer_list_, OnInstallingApp());
248 DVLOG(1) << "BeginInstall... connection = "
249 << net::NetworkChangeNotifier::GetConnectionType();
251 if (IsAppInstalled(profile_, app_id_)) {
256 installer_ = new WebstoreStartupInstaller(
260 base::Bind(&StartupAppLauncher::InstallCallback, AsWeakPtr()));
261 installer_->BeginInstall();
264 void StartupAppLauncher::InstallCallback(bool success,
265 const std::string& error) {
268 // Finish initialization after the callback returns.
269 // So that the app finishes its installation.
270 BrowserThread::PostTask(
273 base::Bind(&StartupAppLauncher::OnReadyToLaunch,
278 LOG(ERROR) << "App install failed: " << error;
279 OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
282 void StartupAppLauncher::OnReadyToLaunch() {
283 ready_to_launch_ = true;
284 FOR_EACH_OBSERVER(Observer, observer_list_, OnReadyToLaunch());
287 void StartupAppLauncher::OnNetworkChanged(
288 net::NetworkChangeNotifier::ConnectionType type) {
289 DVLOG(1) << "OnNetworkChanged... connection = "
290 << net::NetworkChangeNotifier::GetConnectionType();
291 if (!net::NetworkChangeNotifier::IsOffline()) {
292 DVLOG(1) << "Network up and running!";
293 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
297 DVLOG(1) << "Network not running yet!";
301 } // namespace chromeos