kiosk_mode_screensaver.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright (c) 2012 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/kiosk_mode/kiosk_mode_screensaver.h"
6
7#include "ash/screensaver/screensaver_view.h"
8#include "ash/shell.h"
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/lazy_instance.h"
12#include "base/logging.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chrome/browser/chromeos/kiosk_mode/kiosk_mode_settings.h"
16#include "chrome/browser/chromeos/login/existing_user_controller.h"
17#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
18#include "chrome/browser/chromeos/policy/app_pack_updater.h"
19#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
20#include "chrome/browser/chromeos/profiles/profile_helper.h"
21#include "chrome/browser/extensions/extension_garbage_collector.h"
22#include "chrome/browser/extensions/extension_service.h"
23#include "chrome/browser/extensions/sandboxed_unpacker.h"
24#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
25#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
26#include "chromeos/login/login_state.h"
27#include "content/public/browser/browser_thread.h"
28#include "content/public/browser/notification_service.h"
29#include "extensions/browser/extension_system.h"
30#include "extensions/common/extension.h"
31#include "extensions/common/file_util.h"
32#include "ui/wm/core/user_activity_detector.h"
33
34using extensions::Extension;
35using extensions::ExtensionGarbageCollector;
36using extensions::SandboxedUnpacker;
37
38namespace chromeos {
39
40namespace {
41
42ExtensionService* GetDefaultExtensionService() {
43  Profile* default_profile = ProfileHelper::GetSigninProfile();
44  if (!default_profile)
45    return NULL;
46  return extensions::ExtensionSystem::Get(
47      default_profile)->extension_service();
48}
49
50ExtensionGarbageCollector* GetDefaultExtensionGarbageCollector() {
51  Profile* default_profile = ProfileHelper::GetSigninProfile();
52  if (!default_profile)
53    return NULL;
54  return ExtensionGarbageCollector::Get(default_profile);
55}
56
57typedef base::Callback<void(
58    scoped_refptr<Extension>,
59    const base::FilePath&)> UnpackCallback;
60
61class ScreensaverUnpackerClient
62    : public extensions::SandboxedUnpackerClient {
63 public:
64  ScreensaverUnpackerClient(const base::FilePath& crx_path,
65                            const UnpackCallback& unpacker_callback)
66      : crx_path_(crx_path),
67        unpack_callback_(unpacker_callback) {}
68
69  virtual void OnUnpackSuccess(const base::FilePath& temp_dir,
70                               const base::FilePath& extension_root,
71                               const base::DictionaryValue* original_manifest,
72                               const Extension* extension,
73                               const SkBitmap& install_icon) OVERRIDE;
74  virtual void OnUnpackFailure(const base::string16& error) OVERRIDE;
75
76 protected:
77  virtual ~ScreensaverUnpackerClient() {}
78
79 private:
80  void LoadScreensaverExtension(
81      const base::FilePath& extension_base_path,
82      const base::FilePath& screensaver_extension_path);
83
84  void NotifyAppPackOfDamagedFile();
85
86  base::FilePath crx_path_;
87  UnpackCallback unpack_callback_;
88
89  DISALLOW_COPY_AND_ASSIGN(ScreensaverUnpackerClient);
90};
91
92void ScreensaverUnpackerClient::OnUnpackSuccess(
93    const base::FilePath& temp_dir,
94    const base::FilePath& extension_root,
95    const base::DictionaryValue* original_manifest,
96    const Extension* extension,
97    const SkBitmap& install_icon) {
98  content::BrowserThread::PostTask(
99      content::BrowserThread::FILE,
100      FROM_HERE,
101      base::Bind(&ScreensaverUnpackerClient::LoadScreensaverExtension,
102                 this,
103                 temp_dir,
104                 extension_root));
105}
106
107void ScreensaverUnpackerClient::OnUnpackFailure(const base::string16& error) {
108  LOG(ERROR) << "Couldn't unpack screensaver extension. Error: " << error;
109  NotifyAppPackOfDamagedFile();
110}
111
112void ScreensaverUnpackerClient::LoadScreensaverExtension(
113    const base::FilePath& extension_base_path,
114    const base::FilePath& screensaver_extension_path) {
115  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
116
117  // TODO(rkc): This is a HACK, please remove this method from extension
118  // service once this code is deprecated. See crbug.com/280363
119  ExtensionGarbageCollector* gc = GetDefaultExtensionGarbageCollector();
120  if (gc)
121    gc->disable_garbage_collection();
122
123  std::string error;
124  scoped_refptr<Extension> screensaver_extension =
125      extensions::file_util::LoadExtension(screensaver_extension_path,
126                                           extensions::Manifest::COMPONENT,
127                                           Extension::NO_FLAGS,
128                                           &error);
129  if (!screensaver_extension.get()) {
130    LOG(ERROR) << "Could not load screensaver extension from: "
131               << screensaver_extension_path.value() << " due to: " << error;
132    NotifyAppPackOfDamagedFile();
133    return;
134  }
135
136  content::BrowserThread::PostTask(
137      content::BrowserThread::UI,
138      FROM_HERE,
139      base::Bind(
140          unpack_callback_,
141          screensaver_extension,
142          extension_base_path));
143}
144
145void ScreensaverUnpackerClient::NotifyAppPackOfDamagedFile() {
146  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
147    content::BrowserThread::PostTask(
148        content::BrowserThread::UI, FROM_HERE,
149        base::Bind(&ScreensaverUnpackerClient::NotifyAppPackOfDamagedFile,
150                   this));
151    return;
152  }
153
154  policy::BrowserPolicyConnectorChromeOS* connector =
155      g_browser_process->platform_part()->browser_policy_connector_chromeos();
156  policy::AppPackUpdater* updater = connector->GetAppPackUpdater();
157  if (updater)
158    updater->OnDamagedFileDetected(crx_path_);
159}
160
161}  // namespace
162
163KioskModeScreensaver::KioskModeScreensaver()
164    : weak_ptr_factory_(this) {
165  chromeos::KioskModeSettings* kiosk_mode_settings =
166      chromeos::KioskModeSettings::Get();
167
168  if (kiosk_mode_settings->is_initialized()) {
169    GetScreensaverCrxPath();
170  } else {
171    kiosk_mode_settings->Initialize(base::Bind(
172        &KioskModeScreensaver::GetScreensaverCrxPath,
173        weak_ptr_factory_.GetWeakPtr()));
174  }
175}
176
177KioskModeScreensaver::~KioskModeScreensaver() {
178  // If we are shutting down the system might already be gone and we shouldn't
179  // do anything (see crbug.com/288216).
180  if (!g_browser_process || g_browser_process->IsShuttingDown())
181    return;
182
183  // If the extension was unpacked.
184  if (!extension_base_path_.empty()) {
185    // TODO(rkc): This is a HACK, please remove this method from extension
186    // service once this code is deprecated. See crbug.com/280363
187    ExtensionGarbageCollector* gc = GetDefaultExtensionGarbageCollector();
188    if (gc)
189      gc->enable_garbage_collection();
190
191    // Delete it.
192    content::BrowserThread::PostTask(
193        content::BrowserThread::FILE,
194        FROM_HERE,
195        base::Bind(
196            &extensions::file_util::DeleteFile, extension_base_path_, true));
197  }
198
199  // In case we're shutting down without ever triggering the active
200  // notification and/or logging in.
201  if (ash::Shell::GetInstance() &&
202      ash::Shell::GetInstance()->user_activity_detector() &&
203      ash::Shell::GetInstance()->user_activity_detector()->HasObserver(this))
204    ash::Shell::GetInstance()->user_activity_detector()->RemoveObserver(this);
205}
206
207void KioskModeScreensaver::GetScreensaverCrxPath() {
208  chromeos::KioskModeSettings::Get()->GetScreensaverPath(
209      base::Bind(&KioskModeScreensaver::ScreensaverPathCallback,
210                 weak_ptr_factory_.GetWeakPtr()));
211}
212
213void KioskModeScreensaver::ScreensaverPathCallback(
214    const base::FilePath& screensaver_crx) {
215  if (screensaver_crx.empty())
216    return;
217
218  ExtensionService* extension_service = GetDefaultExtensionService();
219  if (!extension_service)
220    return;
221  base::FilePath extensions_dir = extension_service->install_directory();
222  scoped_refptr<SandboxedUnpacker> screensaver_unpacker(
223      new SandboxedUnpacker(
224          screensaver_crx,
225          extensions::Manifest::COMPONENT,
226          Extension::NO_FLAGS,
227          extensions_dir,
228          content::BrowserThread::GetMessageLoopProxyForThread(
229              content::BrowserThread::FILE).get(),
230          new ScreensaverUnpackerClient(
231              screensaver_crx,
232              base::Bind(
233                  &KioskModeScreensaver::SetupScreensaver,
234                  weak_ptr_factory_.GetWeakPtr()))));
235
236  // Fire off the unpacker on the file thread; don't need it to return.
237  content::BrowserThread::PostTask(
238      content::BrowserThread::FILE,
239      FROM_HERE,
240      base::Bind(
241          &SandboxedUnpacker::Start, screensaver_unpacker.get()));
242}
243
244void KioskModeScreensaver::SetupScreensaver(
245    scoped_refptr<Extension> extension,
246    const base::FilePath& extension_base_path) {
247  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
248  extension_base_path_ = extension_base_path;
249
250  // If the user is already logged in, don't need to display the screensaver.
251  if (chromeos::LoginState::Get()->IsUserLoggedIn())
252    return;
253
254  ash::Shell::GetInstance()->user_activity_detector()->AddObserver(this);
255
256  ExtensionService* extension_service = GetDefaultExtensionService();
257  // Add the extension to the extension service and display the screensaver.
258  if (extension_service) {
259    extension_service->AddExtension(extension.get());
260    ash::ShowScreensaver(
261        extensions::AppLaunchInfo::GetFullLaunchURL(extension.get()));
262  } else {
263    LOG(ERROR) << "Couldn't get extension system. Unable to load screensaver!";
264    ShutdownKioskModeScreensaver();
265  }
266}
267
268void KioskModeScreensaver::OnUserActivity(const ui::Event* event) {
269  // We don't want to handle further user notifications; we'll either login
270  // the user and close out or or at least close the screensaver.
271  ash::Shell::GetInstance()->user_activity_detector()->RemoveObserver(this);
272
273  // Find the retail mode login page.
274  if (LoginDisplayHostImpl::default_host()) {
275    LoginDisplayHostImpl* webui_host =
276        static_cast<LoginDisplayHostImpl*>(
277            LoginDisplayHostImpl::default_host());
278    OobeUI* oobe_ui = webui_host->GetOobeUI();
279
280    // Show the login spinner.
281    if (oobe_ui)
282      oobe_ui->ShowRetailModeLoginSpinner();
283
284    // Close the screensaver, our login spinner is already showing.
285    ash::CloseScreensaver();
286
287    // Log us in.
288    ExistingUserController* controller =
289        ExistingUserController::current_controller();
290    if (controller && !chromeos::LoginState::Get()->IsUserLoggedIn())
291      controller->LoginAsRetailModeUser();
292  } else {
293    // No default host for the WebUiLoginDisplay means that we're already in the
294    // process of logging in - shut down screensaver and do nothing else.
295    ash::CloseScreensaver();
296  }
297
298  ShutdownKioskModeScreensaver();
299}
300
301static KioskModeScreensaver* g_kiosk_mode_screensaver = NULL;
302
303void InitializeKioskModeScreensaver() {
304  if (g_kiosk_mode_screensaver) {
305    LOG(WARNING) << "Screensaver was already initialized";
306    return;
307  }
308
309  g_kiosk_mode_screensaver = new KioskModeScreensaver();
310}
311
312void ShutdownKioskModeScreensaver() {
313  delete g_kiosk_mode_screensaver;
314  g_kiosk_mode_screensaver = NULL;
315}
316
317}  // namespace chromeos
318