Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / first_run / drive_first_run_controller.cc
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/first_run/drive_first_run_controller.h"
6
7 #include "ash/shell.h"
8 #include "ash/system/tray/system_tray_delegate.h"
9 #include "base/callback.h"
10 #include "base/memory/weak_ptr.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/background/background_contents_service.h"
15 #include "chrome/browser/background/background_contents_service_factory.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
18 #include "chrome/browser/extensions/extension_service.h"
19 #include "chrome/browser/tab_contents/background_contents.h"
20 #include "chrome/browser/ui/browser_navigator.h"
21 #include "chrome/browser/ui/host_desktop.h"
22 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
23 #include "chrome/browser/ui/singleton_tabs.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "chrome/grit/theme_resources.h"
26 #include "components/user_manager/user_manager.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/navigation_controller.h"
29 #include "content/public/browser/notification_details.h"
30 #include "content/public/browser/notification_observer.h"
31 #include "content/public/browser/notification_registrar.h"
32 #include "content/public/browser/notification_source.h"
33 #include "content/public/browser/notification_types.h"
34 #include "content/public/browser/render_frame_host.h"
35 #include "content/public/browser/site_instance.h"
36 #include "content/public/browser/web_contents.h"
37 #include "content/public/browser/web_contents_observer.h"
38 #include "extensions/browser/extension_registry.h"
39 #include "extensions/browser/extension_system.h"
40 #include "extensions/common/extension.h"
41 #include "extensions/common/extension_set.h"
42 #include "ui/base/l10n/l10n_util.h"
43 #include "ui/base/resource/resource_bundle.h"
44 #include "ui/message_center/message_center.h"
45 #include "ui/message_center/notification.h"
46 #include "ui/message_center/notification_delegate.h"
47 #include "url/gurl.h"
48
49 namespace chromeos {
50
51 namespace {
52
53 // The initial time to wait in seconds before enabling offline mode.
54 int kInitialDelaySeconds = 180;
55
56 // Time to wait for Drive app background page to come up before giving up.
57 int kWebContentsTimeoutSeconds = 15;
58
59 // Google Drive enable offline endpoint.
60 const char kDriveOfflineEndpointUrl[] =
61     "https://docs.google.com/offline/autoenable";
62
63 // Google Drive app id.
64 const char kDriveHostedAppId[] = "apdfllckaahabafndbhieahigkjlhalf";
65
66 // Id of the notification shown when offline mode is enabled.
67 const char kDriveOfflineNotificationId[] = "chrome://drive/enable-offline";
68
69 // The URL of the support page opened when the notification button is clicked.
70 const char kDriveOfflineSupportUrl[] =
71     "https://support.google.com/drive/answer/1628467";
72
73 }  // namespace
74
75 ////////////////////////////////////////////////////////////////////////////////
76 // DriveOfflineNotificationDelegate
77
78 // NotificationDelegate for the notification that is displayed when Drive
79 // offline mode is enabled automatically. Clicking on the notification button
80 // will open the Drive settings page.
81 class DriveOfflineNotificationDelegate
82     : public message_center::NotificationDelegate {
83  public:
84   explicit DriveOfflineNotificationDelegate(Profile* profile)
85       : profile_(profile) {}
86
87   // message_center::NotificationDelegate overrides:
88   virtual void ButtonClick(int button_index) override;
89
90  protected:
91   virtual ~DriveOfflineNotificationDelegate() {}
92
93  private:
94   Profile* profile_;
95
96   DISALLOW_COPY_AND_ASSIGN(DriveOfflineNotificationDelegate);
97 };
98
99 void DriveOfflineNotificationDelegate::ButtonClick(int button_index) {
100   DCHECK_EQ(0, button_index);
101
102   // The support page will be localized based on the user's GAIA account.
103   const GURL url = GURL(kDriveOfflineSupportUrl);
104
105   chrome::ScopedTabbedBrowserDisplayer displayer(
106        profile_,
107        chrome::HOST_DESKTOP_TYPE_ASH);
108   chrome::ShowSingletonTabOverwritingNTP(
109       displayer.browser(),
110       chrome::GetSingletonTabNavigateParams(displayer.browser(), url));
111 }
112
113 ////////////////////////////////////////////////////////////////////////////////
114 // DriveWebContentsManager
115
116 // Manages web contents that initializes Google Drive offline mode. We create
117 // a background WebContents that loads a Drive endpoint to initialize offline
118 // mode. If successful, a background page will be opened to sync the user's
119 // files for offline use.
120 class DriveWebContentsManager : public content::WebContentsObserver,
121                                 public content::WebContentsDelegate,
122                                 public content::NotificationObserver {
123  public:
124   typedef base::Callback<
125       void(bool, DriveFirstRunController::UMAOutcome)> CompletionCallback;
126
127   DriveWebContentsManager(Profile* profile,
128                           const std::string& app_id,
129                           const std::string& endpoint_url,
130                           const CompletionCallback& completion_callback);
131   virtual ~DriveWebContentsManager();
132
133   // Start loading the WebContents for the endpoint in the context of the Drive
134   // hosted app that will initialize offline mode and open a background page.
135   void StartLoad();
136
137   // Stop loading the endpoint. The |completion_callback| will not be called.
138   void StopLoad();
139
140  private:
141   // Called when when offline initialization succeeds or fails and schedules
142   // |RunCompletionCallback|.
143   void OnOfflineInit(bool success,
144                      DriveFirstRunController::UMAOutcome outcome);
145
146   // Runs |completion_callback|.
147   void RunCompletionCallback(bool success,
148                              DriveFirstRunController::UMAOutcome outcome);
149
150   // content::WebContentsObserver overrides:
151   virtual void DidFailProvisionalLoad(
152       content::RenderFrameHost* render_frame_host,
153       const GURL& validated_url,
154       int error_code,
155       const base::string16& error_description) override;
156
157   virtual void DidFailLoad(content::RenderFrameHost* render_frame_host,
158                            const GURL& validated_url,
159                            int error_code,
160                            const base::string16& error_description) override;
161
162   // content::WebContentsDelegate overrides:
163   virtual bool ShouldCreateWebContents(
164       content::WebContents* web_contents,
165       int route_id,
166       WindowContainerType window_container_type,
167       const base::string16& frame_name,
168       const GURL& target_url,
169       const std::string& partition_id,
170       content::SessionStorageNamespace* session_storage_namespace) override;
171
172   // content::NotificationObserver overrides:
173   virtual void Observe(int type,
174                        const content::NotificationSource& source,
175                        const content::NotificationDetails& details) override;
176
177   Profile* profile_;
178   const std::string app_id_;
179   const std::string endpoint_url_;
180   scoped_ptr<content::WebContents> web_contents_;
181   content::NotificationRegistrar registrar_;
182   bool started_;
183   CompletionCallback completion_callback_;
184   base::WeakPtrFactory<DriveWebContentsManager> weak_ptr_factory_;
185
186   DISALLOW_COPY_AND_ASSIGN(DriveWebContentsManager);
187 };
188
189 DriveWebContentsManager::DriveWebContentsManager(
190     Profile* profile,
191     const std::string& app_id,
192     const std::string& endpoint_url,
193     const CompletionCallback& completion_callback)
194     : profile_(profile),
195       app_id_(app_id),
196       endpoint_url_(endpoint_url),
197       started_(false),
198       completion_callback_(completion_callback),
199       weak_ptr_factory_(this) {
200   DCHECK(!completion_callback_.is_null());
201   registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
202                  content::Source<Profile>(profile_));
203 }
204
205 DriveWebContentsManager::~DriveWebContentsManager() {
206 }
207
208 void DriveWebContentsManager::StartLoad() {
209   started_ = true;
210   const GURL url(endpoint_url_);
211   content::WebContents::CreateParams create_params(
212         profile_, content::SiteInstance::CreateForURL(profile_, url));
213
214   web_contents_.reset(content::WebContents::Create(create_params));
215   web_contents_->SetDelegate(this);
216   extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
217       web_contents_.get());
218
219   content::NavigationController::LoadURLParams load_params(url);
220   load_params.transition_type = ui::PAGE_TRANSITION_GENERATED;
221   web_contents_->GetController().LoadURLWithParams(load_params);
222
223   content::WebContentsObserver::Observe(web_contents_.get());
224 }
225
226 void DriveWebContentsManager::StopLoad() {
227   started_ = false;
228 }
229
230 void DriveWebContentsManager::OnOfflineInit(
231     bool success,
232     DriveFirstRunController::UMAOutcome outcome) {
233   if (started_) {
234     // We postpone notifying the controller as we may be in the middle
235     // of a call stack for some routine of the contained WebContents.
236     base::MessageLoop::current()->PostTask(
237         FROM_HERE,
238         base::Bind(&DriveWebContentsManager::RunCompletionCallback,
239                    weak_ptr_factory_.GetWeakPtr(),
240                    success,
241                    outcome));
242     StopLoad();
243   }
244 }
245
246 void DriveWebContentsManager::RunCompletionCallback(
247     bool success,
248     DriveFirstRunController::UMAOutcome outcome) {
249   completion_callback_.Run(success, outcome);
250 }
251
252 void DriveWebContentsManager::DidFailProvisionalLoad(
253     content::RenderFrameHost* render_frame_host,
254     const GURL& validated_url,
255     int error_code,
256     const base::string16& error_description) {
257   if (!render_frame_host->GetParent()) {
258     LOG(WARNING) << "Failed to load WebContents to enable offline mode.";
259     OnOfflineInit(false,
260                   DriveFirstRunController::OUTCOME_WEB_CONTENTS_LOAD_FAILED);
261   }
262 }
263
264 void DriveWebContentsManager::DidFailLoad(
265     content::RenderFrameHost* render_frame_host,
266     const GURL& validated_url,
267     int error_code,
268     const base::string16& error_description) {
269   if (!render_frame_host->GetParent()) {
270     LOG(WARNING) << "Failed to load WebContents to enable offline mode.";
271     OnOfflineInit(false,
272                   DriveFirstRunController::OUTCOME_WEB_CONTENTS_LOAD_FAILED);
273   }
274 }
275
276 bool DriveWebContentsManager::ShouldCreateWebContents(
277     content::WebContents* web_contents,
278     int route_id,
279     WindowContainerType window_container_type,
280     const base::string16& frame_name,
281     const GURL& target_url,
282     const std::string& partition_id,
283     content::SessionStorageNamespace* session_storage_namespace) {
284
285   if (window_container_type == WINDOW_CONTAINER_TYPE_NORMAL)
286     return true;
287
288   // Check that the target URL is for the Drive app.
289   const extensions::Extension* extension =
290       extensions::ExtensionRegistry::Get(profile_)
291           ->enabled_extensions().GetAppByURL(target_url);
292   if (!extension || extension->id() != app_id_)
293     return true;
294
295   // The background contents creation is normally done in Browser, but
296   // because we're using a detached WebContents, we need to do it ourselves.
297   BackgroundContentsService* background_contents_service =
298       BackgroundContentsServiceFactory::GetForProfile(profile_);
299
300   // Prevent redirection if background contents already exists.
301   if (background_contents_service->GetAppBackgroundContents(
302       base::UTF8ToUTF16(app_id_))) {
303     return false;
304   }
305   BackgroundContents* contents = background_contents_service
306       ->CreateBackgroundContents(content::SiteInstance::Create(profile_),
307                                  route_id,
308                                  profile_,
309                                  frame_name,
310                                  base::ASCIIToUTF16(app_id_),
311                                  partition_id,
312                                  session_storage_namespace);
313
314   contents->web_contents()->GetController().LoadURL(
315       target_url,
316       content::Referrer(),
317       ui::PAGE_TRANSITION_LINK,
318       std::string());
319
320   // Return false as we already created the WebContents here.
321   return false;
322 }
323
324 void DriveWebContentsManager::Observe(
325     int type,
326     const content::NotificationSource& source,
327     const content::NotificationDetails& details) {
328   if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED) {
329     const std::string app_id = base::UTF16ToUTF8(
330         content::Details<BackgroundContentsOpenedDetails>(details)
331             ->application_id);
332     if (app_id == app_id_)
333       OnOfflineInit(true, DriveFirstRunController::OUTCOME_OFFLINE_ENABLED);
334   }
335 }
336
337 ////////////////////////////////////////////////////////////////////////////////
338 // DriveFirstRunController
339
340 DriveFirstRunController::DriveFirstRunController(Profile* profile)
341     : profile_(profile),
342       started_(false),
343       initial_delay_secs_(kInitialDelaySeconds),
344       web_contents_timeout_secs_(kWebContentsTimeoutSeconds),
345       drive_offline_endpoint_url_(kDriveOfflineEndpointUrl),
346       drive_hosted_app_id_(kDriveHostedAppId) {
347 }
348
349 DriveFirstRunController::~DriveFirstRunController() {
350 }
351
352 void DriveFirstRunController::EnableOfflineMode() {
353   if (!started_) {
354     started_ = true;
355     initial_delay_timer_.Start(
356       FROM_HERE,
357       base::TimeDelta::FromSeconds(initial_delay_secs_),
358       this,
359       &DriveFirstRunController::EnableOfflineMode);
360     return;
361   }
362
363   if (!user_manager::UserManager::Get()->IsLoggedInAsRegularUser()) {
364     LOG(ERROR) << "Attempting to enable offline access "
365                   "but not logged in a regular user.";
366     OnOfflineInit(false, OUTCOME_WRONG_USER_TYPE);
367     return;
368   }
369
370   ExtensionService* extension_service =
371       extensions::ExtensionSystem::Get(profile_)->extension_service();
372   if (!extension_service->GetExtensionById(drive_hosted_app_id_, false)) {
373     LOG(WARNING) << "Drive app is not installed.";
374     OnOfflineInit(false, OUTCOME_APP_NOT_INSTALLED);
375     return;
376   }
377
378   BackgroundContentsService* background_contents_service =
379       BackgroundContentsServiceFactory::GetForProfile(profile_);
380   if (background_contents_service->GetAppBackgroundContents(
381       base::UTF8ToUTF16(drive_hosted_app_id_))) {
382     LOG(WARNING) << "Background page for Drive app already exists";
383     OnOfflineInit(false, OUTCOME_BACKGROUND_PAGE_EXISTS);
384     return;
385   }
386
387   web_contents_manager_.reset(new DriveWebContentsManager(
388       profile_,
389       drive_hosted_app_id_,
390       drive_offline_endpoint_url_,
391       base::Bind(&DriveFirstRunController::OnOfflineInit,
392                  base::Unretained(this))));
393   web_contents_manager_->StartLoad();
394   web_contents_timer_.Start(
395       FROM_HERE,
396       base::TimeDelta::FromSeconds(web_contents_timeout_secs_),
397       this,
398       &DriveFirstRunController::OnWebContentsTimedOut);
399 }
400
401 void DriveFirstRunController::AddObserver(Observer* observer) {
402   observer_list_.AddObserver(observer);
403 }
404
405 void DriveFirstRunController::RemoveObserver(Observer* observer) {
406   observer_list_.RemoveObserver(observer);
407 }
408
409 void DriveFirstRunController::SetDelaysForTest(int initial_delay_secs,
410                                                int timeout_secs) {
411   DCHECK(!started_);
412   initial_delay_secs_ = initial_delay_secs;
413   web_contents_timeout_secs_ = timeout_secs;
414 }
415
416 void DriveFirstRunController::SetAppInfoForTest(
417     const std::string& app_id,
418     const std::string& endpoint_url) {
419   DCHECK(!started_);
420   drive_hosted_app_id_ = app_id;
421   drive_offline_endpoint_url_ = endpoint_url;
422 }
423
424 void DriveFirstRunController::OnWebContentsTimedOut() {
425   LOG(WARNING) << "Timed out waiting for web contents.";
426   FOR_EACH_OBSERVER(Observer, observer_list_, OnTimedOut());
427   OnOfflineInit(false, OUTCOME_WEB_CONTENTS_TIMED_OUT);
428 }
429
430 void DriveFirstRunController::CleanUp() {
431   if (web_contents_manager_)
432     web_contents_manager_->StopLoad();
433   web_contents_timer_.Stop();
434   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
435 }
436
437 void DriveFirstRunController::OnOfflineInit(bool success, UMAOutcome outcome) {
438   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
439   if (success)
440     ShowNotification();
441   UMA_HISTOGRAM_ENUMERATION("DriveOffline.CrosAutoEnableOutcome",
442                             outcome, OUTCOME_MAX);
443   FOR_EACH_OBSERVER(Observer, observer_list_, OnCompletion(success));
444   CleanUp();
445 }
446
447 void DriveFirstRunController::ShowNotification() {
448   ExtensionService* service =
449       extensions::ExtensionSystem::Get(profile_)->extension_service();
450   DCHECK(service);
451   const extensions::Extension* extension =
452       service->GetExtensionById(drive_hosted_app_id_, false);
453   DCHECK(extension);
454
455   message_center::RichNotificationData data;
456   data.buttons.push_back(message_center::ButtonInfo(
457       l10n_util::GetStringUTF16(IDS_DRIVE_OFFLINE_NOTIFICATION_BUTTON)));
458   ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
459   scoped_ptr<message_center::Notification> notification(
460       new message_center::Notification(
461           message_center::NOTIFICATION_TYPE_SIMPLE,
462           kDriveOfflineNotificationId,
463           base::string16(), // title
464           l10n_util::GetStringUTF16(IDS_DRIVE_OFFLINE_NOTIFICATION_MESSAGE),
465           resource_bundle.GetImageNamed(IDR_NOTIFICATION_DRIVE),
466           base::UTF8ToUTF16(extension->name()),
467           message_center::NotifierId(message_center::NotifierId::APPLICATION,
468                                      kDriveHostedAppId),
469           data,
470           new DriveOfflineNotificationDelegate(profile_)));
471   notification->set_priority(message_center::LOW_PRIORITY);
472   message_center::MessageCenter::Get()->AddNotification(notification.Pass());
473 }
474
475 }  // namespace chromeos