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