startup_app_launcher.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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 "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_enabled_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"
34
35using content::BrowserThread;
36using extensions::Extension;
37using extensions::WebstoreStartupInstaller;
38
39namespace chromeos {
40
41namespace {
42
43const char kOAuthRefreshToken[] = "refresh_token";
44const char kOAuthClientId[] = "client_id";
45const char kOAuthClientSecret[] = "client_secret";
46
47const base::FilePath::CharType kOAuthFileName[] =
48    FILE_PATH_LITERAL("kiosk_auth");
49
50bool IsAppInstalled(Profile* profile, const std::string& app_id) {
51  return extensions::ExtensionSystem::Get(profile)->extension_service()->
52      GetInstalledExtension(app_id);
53}
54
55}  // namespace
56
57
58StartupAppLauncher::StartupAppLauncher(Profile* profile,
59                                       const std::string& app_id)
60    : profile_(profile),
61      app_id_(app_id),
62      ready_to_launch_(false) {
63  DCHECK(profile_);
64  DCHECK(Extension::IdIsValid(app_id_));
65}
66
67StartupAppLauncher::~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);
73}
74
75void StartupAppLauncher::Initialize() {
76  DVLOG(1) << "Starting... connection = "
77           <<  net::NetworkChangeNotifier::GetConnectionType();
78  StartLoadingOAuthFile();
79}
80
81void StartupAppLauncher::AddObserver(Observer* observer) {
82  observer_list_.AddObserver(observer);
83}
84
85void StartupAppLauncher::RemoveObserver(Observer* observer) {
86  observer_list_.RemoveObserver(observer);
87}
88
89void StartupAppLauncher::StartLoadingOAuthFile() {
90  FOR_EACH_OBSERVER(Observer, observer_list_, OnLoadingOAuthFile());
91
92  KioskOAuthParams* auth_params = new KioskOAuthParams();
93  BrowserThread::PostBlockingPoolTaskAndReply(
94      FROM_HERE,
95      base::Bind(&StartupAppLauncher::LoadOAuthFileOnBlockingPool,
96                 auth_params),
97      base::Bind(&StartupAppLauncher::OnOAuthFileLoaded,
98                 AsWeakPtr(),
99                 base::Owned(auth_params)));
100}
101
102// static.
103void 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();
118    return;
119  }
120
121  dict->GetString(kOAuthRefreshToken, &auth_params->refresh_token);
122  dict->GetString(kOAuthClientId, &auth_params->client_id);
123  dict->GetString(kOAuthClientSecret, &auth_params->client_secret);
124}
125
126void 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);
134  }
135
136  // If we are restarting chrome (i.e. on crash), we need to initialize
137  // TokenService as well.
138  InitializeTokenService();
139}
140
141void StartupAppLauncher::InitializeNetwork() {
142  FOR_EACH_OBSERVER(Observer, observer_list_, OnInitializingNetwork());
143
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());
149}
150
151void StartupAppLauncher::InitializeTokenService() {
152  FOR_EACH_OBSERVER(Observer, observer_list_, OnInitializingTokenService());
153
154  ProfileOAuth2TokenService* profile_token_service =
155      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
156  if (profile_token_service->RefreshTokenIsAvailable()) {
157    InitializeNetwork();
158    return;
159  }
160
161  // At the end of this method, the execution will be put on hold until
162  // ProfileOAuth2TokenService triggers either OnRefreshTokenAvailable or
163  // OnRefreshTokensLoaded. Given that we want to handle exactly one event,
164  // whichever comes first, both handlers call RemoveObserver on PO2TS. Handling
165  // any of the two events is the only way to resume the execution and enable
166  // Cleanup method to be called, self-invoking a destructor. In destructor
167  // StartupAppLauncher is no longer an observer of PO2TS and there is no need
168  // to call RemoveObserver again.
169  profile_token_service->AddObserver(this);
170
171  TokenService* token_service = TokenServiceFactory::GetForProfile(profile_);
172  token_service->Initialize(GaiaConstants::kChromeSource, profile_);
173
174  // Pass oauth2 refresh token from the auth file.
175  // TODO(zelidrag): We should probably remove this option after M27.
176  // TODO(fgorski): This can go when we have persistence implemented on PO2TS.
177  // Unless the code is no longer needed.
178  if (!auth_params_.refresh_token.empty()) {
179    token_service->UpdateCredentialsWithOAuth2(
180        GaiaAuthConsumer::ClientOAuthResult(
181            auth_params_.refresh_token,
182            std::string(),  // access_token
183            0));            // new_expires_in_secs
184  } else {
185    // Load whatever tokens we have stored there last time around.
186    token_service->LoadTokensFromDB();
187  }
188}
189
190void StartupAppLauncher::OnRefreshTokenAvailable(
191    const std::string& account_id) {
192  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
193      ->RemoveObserver(this);
194  InitializeNetwork();
195}
196
197void StartupAppLauncher::OnRefreshTokensLoaded() {
198  ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)
199      ->RemoveObserver(this);
200  InitializeNetwork();
201}
202
203void StartupAppLauncher::OnLaunchSuccess() {
204  FOR_EACH_OBSERVER(Observer, observer_list_, OnLaunchSucceeded());
205}
206
207void StartupAppLauncher::OnLaunchFailure(KioskAppLaunchError::Error error) {
208  LOG(ERROR) << "App launch failed, error: " << error;
209  DCHECK_NE(KioskAppLaunchError::NONE, error);
210
211  FOR_EACH_OBSERVER(Observer, observer_list_, OnLaunchFailed(error));
212}
213
214void StartupAppLauncher::LaunchApp() {
215  if (!ready_to_launch_) {
216    NOTREACHED();
217    LOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
218  }
219
220  const Extension* extension = extensions::ExtensionSystem::Get(profile_)->
221      extension_service()->GetInstalledExtension(app_id_);
222  CHECK(extension);
223
224  if (!extensions::KioskEnabledInfo::IsKioskEnabled(extension)) {
225    OnLaunchFailure(KioskAppLaunchError::NOT_KIOSK_ENABLED);
226    return;
227  }
228
229  // Always open the app in a window.
230  chrome::OpenApplication(chrome::AppLaunchParams(profile_,
231                                                  extension,
232                                                  extension_misc::LAUNCH_WINDOW,
233                                                  NEW_WINDOW));
234  InitAppSession(profile_, app_id_);
235
236  UserManager::Get()->SessionStarted();
237
238  content::NotificationService::current()->Notify(
239      chrome::NOTIFICATION_KIOSK_APP_LAUNCHED,
240      content::NotificationService::AllSources(),
241      content::NotificationService::NoDetails());
242
243  OnLaunchSuccess();
244}
245
246void StartupAppLauncher::BeginInstall() {
247  FOR_EACH_OBSERVER(Observer, observer_list_, OnInstallingApp());
248
249  DVLOG(1) << "BeginInstall... connection = "
250           <<  net::NetworkChangeNotifier::GetConnectionType();
251
252  if (IsAppInstalled(profile_, app_id_)) {
253    OnReadyToLaunch();
254    return;
255  }
256
257  installer_ = new WebstoreStartupInstaller(
258      app_id_,
259      profile_,
260      false,
261      base::Bind(&StartupAppLauncher::InstallCallback, AsWeakPtr()));
262  installer_->BeginInstall();
263}
264
265void StartupAppLauncher::InstallCallback(bool success,
266                                         const std::string& error) {
267  installer_ = NULL;
268  if (success) {
269    // Finish initialization after the callback returns.
270    // So that the app finishes its installation.
271    BrowserThread::PostTask(
272        BrowserThread::UI,
273        FROM_HERE,
274        base::Bind(&StartupAppLauncher::OnReadyToLaunch,
275                   AsWeakPtr()));
276    return;
277  }
278
279  LOG(ERROR) << "App install failed: " << error;
280  OnLaunchFailure(KioskAppLaunchError::UNABLE_TO_INSTALL);
281}
282
283void StartupAppLauncher::OnReadyToLaunch() {
284  ready_to_launch_ = true;
285  FOR_EACH_OBSERVER(Observer, observer_list_, OnReadyToLaunch());
286}
287
288void StartupAppLauncher::OnNetworkChanged(
289    net::NetworkChangeNotifier::ConnectionType type) {
290  DVLOG(1) << "OnNetworkChanged... connection = "
291           <<  net::NetworkChangeNotifier::GetConnectionType();
292  if (!net::NetworkChangeNotifier::IsOffline()) {
293    DVLOG(1) << "Network up and running!";
294    net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
295
296    BeginInstall();
297  } else {
298    DVLOG(1) << "Network not running yet!";
299  }
300}
301
302}   // namespace chromeos
303