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_chromeos.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::ExtensionGarbageCollectorChromeOS;
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
50ExtensionGarbageCollectorChromeOS* GetDefaultExtensionGarbageCollector() {
51  Profile* default_profile = ProfileHelper::GetSigninProfile();
52  if (!default_profile)
53    return NULL;
54  return ExtensionGarbageCollectorChromeOS::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  ExtensionGarbageCollectorChromeOS* 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    ExtensionGarbageCollectorChromeOS* gc =
188        GetDefaultExtensionGarbageCollector();
189    if (gc)
190      gc->enable_garbage_collection();
191
192    // Delete it.
193    content::BrowserThread::PostTask(
194        content::BrowserThread::FILE,
195        FROM_HERE,
196        base::Bind(
197            &extensions::file_util::DeleteFile, extension_base_path_, true));
198  }
199
200  // In case we're shutting down without ever triggering the active
201  // notification and/or logging in.
202  if (ash::Shell::GetInstance() &&
203      ash::Shell::GetInstance()->user_activity_detector() &&
204      ash::Shell::GetInstance()->user_activity_detector()->HasObserver(this))
205    ash::Shell::GetInstance()->user_activity_detector()->RemoveObserver(this);
206}
207
208void KioskModeScreensaver::GetScreensaverCrxPath() {
209  chromeos::KioskModeSettings::Get()->GetScreensaverPath(
210      base::Bind(&KioskModeScreensaver::ScreensaverPathCallback,
211                 weak_ptr_factory_.GetWeakPtr()));
212}
213
214void KioskModeScreensaver::ScreensaverPathCallback(
215    const base::FilePath& screensaver_crx) {
216  if (screensaver_crx.empty())
217    return;
218
219  ExtensionService* extension_service = GetDefaultExtensionService();
220  if (!extension_service)
221    return;
222  base::FilePath extensions_dir = extension_service->install_directory();
223  scoped_refptr<SandboxedUnpacker> screensaver_unpacker(
224      new SandboxedUnpacker(
225          screensaver_crx,
226          extensions::Manifest::COMPONENT,
227          Extension::NO_FLAGS,
228          extensions_dir,
229          content::BrowserThread::GetMessageLoopProxyForThread(
230              content::BrowserThread::FILE).get(),
231          new ScreensaverUnpackerClient(
232              screensaver_crx,
233              base::Bind(
234                  &KioskModeScreensaver::SetupScreensaver,
235                  weak_ptr_factory_.GetWeakPtr()))));
236
237  // Fire off the unpacker on the file thread; don't need it to return.
238  content::BrowserThread::PostTask(
239      content::BrowserThread::FILE,
240      FROM_HERE,
241      base::Bind(
242          &SandboxedUnpacker::Start, screensaver_unpacker.get()));
243}
244
245void KioskModeScreensaver::SetupScreensaver(
246    scoped_refptr<Extension> extension,
247    const base::FilePath& extension_base_path) {
248  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
249  extension_base_path_ = extension_base_path;
250
251  // If the user is already logged in, don't need to display the screensaver.
252  if (chromeos::LoginState::Get()->IsUserLoggedIn())
253    return;
254
255  ash::Shell::GetInstance()->user_activity_detector()->AddObserver(this);
256
257  ExtensionService* extension_service = GetDefaultExtensionService();
258  // Add the extension to the extension service and display the screensaver.
259  if (extension_service) {
260    extension_service->AddExtension(extension.get());
261    ash::ShowScreensaver(
262        extensions::AppLaunchInfo::GetFullLaunchURL(extension.get()));
263  } else {
264    LOG(ERROR) << "Couldn't get extension system. Unable to load screensaver!";
265    ShutdownKioskModeScreensaver();
266  }
267}
268
269void KioskModeScreensaver::OnUserActivity(const ui::Event* event) {
270  // We don't want to handle further user notifications; we'll either login
271  // the user and close out or or at least close the screensaver.
272  ash::Shell::GetInstance()->user_activity_detector()->RemoveObserver(this);
273
274  // Find the retail mode login page.
275  if (LoginDisplayHostImpl::default_host()) {
276    LoginDisplayHostImpl* webui_host =
277        static_cast<LoginDisplayHostImpl*>(
278            LoginDisplayHostImpl::default_host());
279    OobeUI* oobe_ui = webui_host->GetOobeUI();
280
281    // Show the login spinner.
282    if (oobe_ui)
283      oobe_ui->ShowRetailModeLoginSpinner();
284
285    // Close the screensaver, our login spinner is already showing.
286    ash::CloseScreensaver();
287
288    // Log us in.
289    ExistingUserController* controller =
290        ExistingUserController::current_controller();
291    if (controller && !chromeos::LoginState::Get()->IsUserLoggedIn())
292      controller->LoginAsRetailModeUser();
293  } else {
294    // No default host for the WebUiLoginDisplay means that we're already in the
295    // process of logging in - shut down screensaver and do nothing else.
296    ash::CloseScreensaver();
297  }
298
299  ShutdownKioskModeScreensaver();
300}
301
302static KioskModeScreensaver* g_kiosk_mode_screensaver = NULL;
303
304void InitializeKioskModeScreensaver() {
305  if (g_kiosk_mode_screensaver) {
306    LOG(WARNING) << "Screensaver was already initialized";
307    return;
308  }
309
310  g_kiosk_mode_screensaver = new KioskModeScreensaver();
311}
312
313void ShutdownKioskModeScreensaver() {
314  delete g_kiosk_mode_screensaver;
315  g_kiosk_mode_screensaver = NULL;
316}
317
318}  // namespace chromeos
319