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