app_launch_controller.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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/login/app_launch_controller.h"
6
7#include "apps/app_window.h"
8#include "apps/app_window_registry.h"
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/files/file_path.h"
12#include "base/json/json_file_value_serializer.h"
13#include "base/logging.h"
14#include "base/memory/weak_ptr.h"
15#include "base/message_loop/message_loop.h"
16#include "base/time/time.h"
17#include "base/values.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/chrome_notification_types.h"
20#include "chrome/browser/chromeos/app_mode/app_session_lifetime.h"
21#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
22#include "chrome/browser/chromeos/app_mode/startup_app_launcher.h"
23#include "chrome/browser/chromeos/login/screens/error_screen_actor.h"
24#include "chrome/browser/chromeos/login/ui/login_display_host.h"
25#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
26#include "chrome/browser/chromeos/login/ui/oobe_display.h"
27#include "chrome/browser/chromeos/login/ui/webui_login_view.h"
28#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
29#include "chrome/browser/chromeos/settings/cros_settings.h"
30#include "chrome/browser/lifetime/application_lifetime.h"
31#include "chrome/browser/profiles/profile.h"
32#include "chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h"
33#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
34#include "content/public/browser/notification_service.h"
35#include "net/base/network_change_notifier.h"
36
37namespace chromeos {
38
39namespace {
40
41// Application install splash screen minimum show time in milliseconds.
42const int kAppInstallSplashScreenMinTimeMS = 3000;
43
44}  // namespace
45
46// static
47bool AppLaunchController::skip_splash_wait_ = false;
48int AppLaunchController::network_wait_time_ = 10;
49base::Closure* AppLaunchController::network_timeout_callback_ = NULL;
50AppLaunchController::ReturnBoolCallback*
51    AppLaunchController::can_configure_network_callback_ = NULL;
52AppLaunchController::ReturnBoolCallback*
53    AppLaunchController::need_owner_auth_to_configure_network_callback_ = NULL;
54
55////////////////////////////////////////////////////////////////////////////////
56// AppLaunchController::AppWindowWatcher
57
58class AppLaunchController::AppWindowWatcher
59    : public apps::AppWindowRegistry::Observer {
60 public:
61  explicit AppWindowWatcher(AppLaunchController* controller,
62                            const std::string& app_id)
63      : controller_(controller),
64        app_id_(app_id),
65        window_registry_(apps::AppWindowRegistry::Get(controller->profile_)),
66        weak_factory_(this) {
67    if (!window_registry_->GetAppWindowsForApp(app_id).empty()) {
68      base::MessageLoop::current()->PostTask(
69          FROM_HERE,
70          base::Bind(&AppWindowWatcher::NotifyAppWindowCreated,
71                     weak_factory_.GetWeakPtr()));
72      return;
73    } else {
74      window_registry_->AddObserver(this);
75    }
76  }
77  virtual ~AppWindowWatcher() {
78    window_registry_->RemoveObserver(this);
79  }
80
81 private:
82  // apps::AppWindowRegistry::Observer overrides:
83  virtual void OnAppWindowAdded(apps::AppWindow* app_window) OVERRIDE {
84    if (app_window->extension_id() == app_id_) {
85      window_registry_->RemoveObserver(this);
86      NotifyAppWindowCreated();
87    }
88  }
89
90  void NotifyAppWindowCreated() {
91    controller_->OnAppWindowCreated();
92  }
93
94  AppLaunchController* controller_;
95  std::string app_id_;
96  apps::AppWindowRegistry* window_registry_;
97  base::WeakPtrFactory<AppWindowWatcher> weak_factory_;
98
99  DISALLOW_COPY_AND_ASSIGN(AppWindowWatcher);
100};
101
102////////////////////////////////////////////////////////////////////////////////
103// AppLaunchController
104
105AppLaunchController::AppLaunchController(const std::string& app_id,
106                                         bool diagnostic_mode,
107                                         LoginDisplayHost* host,
108                                         OobeDisplay* oobe_display)
109    : profile_(NULL),
110      app_id_(app_id),
111      diagnostic_mode_(diagnostic_mode),
112      host_(host),
113      oobe_display_(oobe_display),
114      app_launch_splash_screen_actor_(
115          oobe_display_->GetAppLaunchSplashScreenActor()),
116      webui_visible_(false),
117      launcher_ready_(false),
118      waiting_for_network_(false),
119      network_wait_timedout_(false),
120      showing_network_dialog_(false),
121      network_config_requested_(false),
122      launch_splash_start_time_(0) {
123}
124
125AppLaunchController::~AppLaunchController() {
126  app_launch_splash_screen_actor_->SetDelegate(NULL);
127}
128
129void AppLaunchController::StartAppLaunch() {
130  DVLOG(1) << "Starting kiosk mode...";
131
132  webui_visible_ = host_->GetWebUILoginView()->webui_visible();
133  if (!webui_visible_) {
134    registrar_.Add(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
135                   content::NotificationService::AllSources());
136  }
137  launch_splash_start_time_ = base::TimeTicks::Now().ToInternalValue();
138
139  // TODO(tengs): Add a loading profile app launch state.
140  app_launch_splash_screen_actor_->SetDelegate(this);
141  app_launch_splash_screen_actor_->Show(app_id_);
142
143  KioskAppManager::App app;
144  CHECK(KioskAppManager::Get());
145  CHECK(KioskAppManager::Get()->GetApp(app_id_, &app));
146  kiosk_profile_loader_.reset(
147      new KioskProfileLoader(app.user_id, false, this));
148  kiosk_profile_loader_->Start();
149}
150
151// static
152void AppLaunchController::SkipSplashWaitForTesting() {
153  skip_splash_wait_ = true;
154}
155
156// static
157void AppLaunchController::SetNetworkWaitForTesting(int wait_time_secs) {
158  network_wait_time_ = wait_time_secs;
159}
160
161// static
162void AppLaunchController::SetNetworkTimeoutCallbackForTesting(
163    base::Closure* callback) {
164  network_timeout_callback_ = callback;
165}
166
167// static
168void AppLaunchController::SetCanConfigureNetworkCallbackForTesting(
169    ReturnBoolCallback* can_configure_network_callback) {
170  can_configure_network_callback_ = can_configure_network_callback;
171}
172
173// static
174void AppLaunchController::SetNeedOwnerAuthToConfigureNetworkCallbackForTesting(
175    ReturnBoolCallback* need_owner_auth_callback) {
176  need_owner_auth_to_configure_network_callback_ = need_owner_auth_callback;
177}
178
179void AppLaunchController::OnConfigureNetwork() {
180  DCHECK(profile_);
181  showing_network_dialog_ = true;
182  if (CanConfigureNetwork() && NeedOwnerAuthToConfigureNetwork()) {
183    signin_screen_.reset(new AppLaunchSigninScreen(
184       static_cast<OobeUI*>(oobe_display_), this));
185    signin_screen_->Show();
186  } else {
187    // If kiosk mode was configured through enterprise policy, we may
188    // not have an owner user.
189    // TODO(tengs): We need to figure out the appropriate security meausres
190    // for this case.
191    NOTREACHED();
192  }
193}
194
195void AppLaunchController::OnOwnerSigninSuccess() {
196  app_launch_splash_screen_actor_->ShowNetworkConfigureUI();
197  signin_screen_.reset();
198}
199
200void AppLaunchController::Observe(
201    int type,
202    const content::NotificationSource& source,
203    const content::NotificationDetails& details) {
204  DCHECK_EQ(chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, type);
205  DCHECK(!webui_visible_);
206  webui_visible_ = true;
207  launch_splash_start_time_ = base::TimeTicks::Now().ToInternalValue();
208  if (launcher_ready_)
209    OnReadyToLaunch();
210}
211
212void AppLaunchController::OnCancelAppLaunch() {
213  if (KioskAppManager::Get()->GetDisableBailoutShortcut())
214    return;
215
216  OnLaunchFailed(KioskAppLaunchError::USER_CANCEL);
217}
218
219void AppLaunchController::OnNetworkConfigRequested(bool requested) {
220  network_config_requested_ = requested;
221  if (requested)
222    MaybeShowNetworkConfigureUI();
223  else
224    startup_app_launcher_->RestartLauncher();
225}
226
227void AppLaunchController::OnNetworkStateChanged(bool online) {
228  if (!waiting_for_network_)
229    return;
230
231  if (online && !network_config_requested_)
232    startup_app_launcher_->ContinueWithNetworkReady();
233  else if (network_wait_timedout_)
234    MaybeShowNetworkConfigureUI();
235}
236
237void AppLaunchController::OnProfileLoaded(Profile* profile) {
238  DVLOG(1) << "Profile loaded... Starting app launch.";
239  profile_ = profile;
240
241  // This is needed to trigger input method extensions being loaded.
242  profile_->InitChromeOSPreferences();
243
244  kiosk_profile_loader_.reset();
245  startup_app_launcher_.reset(
246      new StartupAppLauncher(profile_, app_id_, diagnostic_mode_, this));
247  startup_app_launcher_->Initialize();
248}
249
250void AppLaunchController::OnProfileLoadFailed(
251    KioskAppLaunchError::Error error) {
252  OnLaunchFailed(error);
253}
254
255void AppLaunchController::CleanUp() {
256  kiosk_profile_loader_.reset();
257  startup_app_launcher_.reset();
258  splash_wait_timer_.Stop();
259
260  if (host_)
261    host_->Finalize();
262}
263
264void AppLaunchController::OnNetworkWaitTimedout() {
265  DCHECK(waiting_for_network_);
266  LOG(WARNING) << "OnNetworkWaitTimedout... connection = "
267               <<  net::NetworkChangeNotifier::GetConnectionType();
268  network_wait_timedout_ = true;
269
270  MaybeShowNetworkConfigureUI();
271
272  if (network_timeout_callback_)
273    network_timeout_callback_->Run();
274}
275
276void AppLaunchController::OnAppWindowCreated() {
277  DVLOG(1) << "App window created, closing splash screen.";
278  CleanUp();
279}
280
281bool AppLaunchController::CanConfigureNetwork() {
282  if (can_configure_network_callback_)
283    return can_configure_network_callback_->Run();
284
285  policy::BrowserPolicyConnectorChromeOS* connector =
286      g_browser_process->platform_part()->browser_policy_connector_chromeos();
287  if (connector->IsEnterpriseManaged()) {
288    bool should_prompt;
289    if (CrosSettings::Get()->GetBoolean(
290            kAccountsPrefDeviceLocalAccountPromptForNetworkWhenOffline,
291            &should_prompt)) {
292      return should_prompt;
293    }
294
295    // Default to true to allow network configuration if the policy is missing.
296    return true;
297  }
298
299  return !UserManager::Get()->GetOwnerEmail().empty();
300}
301
302bool AppLaunchController::NeedOwnerAuthToConfigureNetwork() {
303  if (need_owner_auth_to_configure_network_callback_)
304    return need_owner_auth_to_configure_network_callback_->Run();
305
306  policy::BrowserPolicyConnectorChromeOS* connector =
307      g_browser_process->platform_part()->browser_policy_connector_chromeos();
308  return !connector->IsEnterpriseManaged();
309}
310
311void AppLaunchController::MaybeShowNetworkConfigureUI() {
312  if (CanConfigureNetwork()) {
313    if (NeedOwnerAuthToConfigureNetwork()) {
314      if (network_config_requested_)
315        OnConfigureNetwork();
316      else
317        app_launch_splash_screen_actor_->ToggleNetworkConfig(true);
318    } else {
319      showing_network_dialog_ = true;
320      app_launch_splash_screen_actor_->ShowNetworkConfigureUI();
321    }
322  } else {
323    app_launch_splash_screen_actor_->UpdateAppLaunchState(
324        AppLaunchSplashScreenActor::APP_LAUNCH_STATE_NETWORK_WAIT_TIMEOUT);
325  }
326}
327
328void AppLaunchController::InitializeNetwork() {
329  // Show the network configuration dialog if network is not initialized
330  // after a brief wait time.
331  waiting_for_network_ = true;
332  network_wait_timer_.Start(
333      FROM_HERE,
334      base::TimeDelta::FromSeconds(network_wait_time_),
335      this, &AppLaunchController::OnNetworkWaitTimedout);
336
337  app_launch_splash_screen_actor_->UpdateAppLaunchState(
338      AppLaunchSplashScreenActor::APP_LAUNCH_STATE_PREPARING_NETWORK);
339}
340
341bool AppLaunchController::IsNetworkReady() {
342  return app_launch_splash_screen_actor_->IsNetworkReady();
343}
344
345void AppLaunchController::OnLoadingOAuthFile() {
346  app_launch_splash_screen_actor_->UpdateAppLaunchState(
347      AppLaunchSplashScreenActor::APP_LAUNCH_STATE_LOADING_AUTH_FILE);
348}
349
350void AppLaunchController::OnInitializingTokenService() {
351  app_launch_splash_screen_actor_->UpdateAppLaunchState(
352      AppLaunchSplashScreenActor::APP_LAUNCH_STATE_LOADING_TOKEN_SERVICE);
353}
354
355void AppLaunchController::OnInstallingApp() {
356  app_launch_splash_screen_actor_->UpdateAppLaunchState(
357      AppLaunchSplashScreenActor::APP_LAUNCH_STATE_INSTALLING_APPLICATION);
358
359  waiting_for_network_ = false;
360  network_wait_timer_.Stop();
361  app_launch_splash_screen_actor_->ToggleNetworkConfig(false);
362
363  // We have connectivity at this point, so we can skip the network
364  // configuration dialog if it is being shown.
365  if (showing_network_dialog_) {
366    app_launch_splash_screen_actor_->Show(app_id_);
367    showing_network_dialog_ = false;
368    launch_splash_start_time_ = base::TimeTicks::Now().ToInternalValue();
369  }
370}
371
372void AppLaunchController::OnReadyToLaunch() {
373  launcher_ready_ = true;
374
375  if (network_config_requested_)
376    return;
377
378  if (!webui_visible_)
379    return;
380
381  if (splash_wait_timer_.IsRunning())
382    return;
383
384  const int64 time_taken_ms = (base::TimeTicks::Now() -
385      base::TimeTicks::FromInternalValue(launch_splash_start_time_)).
386      InMilliseconds();
387
388  // Enforce that we show app install splash screen for some minimum amount
389  // of time.
390  if (!skip_splash_wait_ && time_taken_ms < kAppInstallSplashScreenMinTimeMS) {
391    splash_wait_timer_.Start(
392        FROM_HERE,
393        base::TimeDelta::FromMilliseconds(
394            kAppInstallSplashScreenMinTimeMS - time_taken_ms),
395        this,
396        &AppLaunchController::OnReadyToLaunch);
397    return;
398  }
399
400  startup_app_launcher_->LaunchApp();
401}
402
403void AppLaunchController::OnLaunchSucceeded() {
404  DVLOG(1) << "Kiosk launch succeeded, wait for app window.";
405  app_launch_splash_screen_actor_->UpdateAppLaunchState(
406      AppLaunchSplashScreenActor::APP_LAUNCH_STATE_WAITING_APP_WINDOW);
407
408  DCHECK(!app_window_watcher_);
409  app_window_watcher_.reset(new AppWindowWatcher(this, app_id_));
410}
411
412void AppLaunchController::OnLaunchFailed(KioskAppLaunchError::Error error) {
413  LOG(ERROR) << "Kiosk launch failed. Will now shut down."
414             << ", error=" << error;
415  DCHECK_NE(KioskAppLaunchError::NONE, error);
416
417  // Saves the error and ends the session to go back to login screen.
418  KioskAppLaunchError::Save(error);
419  chrome::AttemptUserExit();
420  CleanUp();
421}
422
423bool AppLaunchController::IsShowingNetworkConfigScreen() {
424  return network_config_requested_;
425}
426
427}   // namespace chromeos
428