Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / chromeos / first_run / drive_first_run_controller.cc
blobae66cfaf11ef039feea1b7b092bb896c0ac8b974
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.
5 #include "chrome/browser/chromeos/first_run/drive_first_run_controller.h"
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.h"
15 #include "chrome/browser/background/background_contents_service.h"
16 #include "chrome/browser/background/background_contents_service_factory.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
19 #include "chrome/browser/extensions/extension_service.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"
49 namespace chromeos {
51 namespace {
53 // The initial time to wait in seconds before enabling offline mode.
54 int kInitialDelaySeconds = 180;
56 // Time to wait for Drive app background page to come up before giving up.
57 int kWebContentsTimeoutSeconds = 15;
59 // Google Drive enable offline endpoint.
60 const char kDriveOfflineEndpointUrl[] =
61 "https://docs.google.com/offline/autoenable";
63 // Google Drive app id.
64 const char kDriveHostedAppId[] = "apdfllckaahabafndbhieahigkjlhalf";
66 // Id of the notification shown when offline mode is enabled.
67 const char kDriveOfflineNotificationId[] = "chrome://drive/enable-offline";
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";
73 } // namespace
75 ////////////////////////////////////////////////////////////////////////////////
76 // DriveOfflineNotificationDelegate
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) {}
87 // message_center::NotificationDelegate overrides:
88 void ButtonClick(int button_index) override;
90 protected:
91 ~DriveOfflineNotificationDelegate() override {}
93 private:
94 Profile* profile_;
96 DISALLOW_COPY_AND_ASSIGN(DriveOfflineNotificationDelegate);
99 void DriveOfflineNotificationDelegate::ButtonClick(int button_index) {
100 DCHECK_EQ(0, button_index);
102 // The support page will be localized based on the user's GAIA account.
103 const GURL url = GURL(kDriveOfflineSupportUrl);
105 chrome::ScopedTabbedBrowserDisplayer displayer(
106 profile_,
107 chrome::HOST_DESKTOP_TYPE_ASH);
108 chrome::ShowSingletonTabOverwritingNTP(
109 displayer.browser(),
110 chrome::GetSingletonTabNavigateParams(displayer.browser(), url));
113 ////////////////////////////////////////////////////////////////////////////////
114 // DriveWebContentsManager
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;
127 DriveWebContentsManager(Profile* profile,
128 const std::string& app_id,
129 const std::string& endpoint_url,
130 const CompletionCallback& completion_callback);
131 ~DriveWebContentsManager() override;
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();
137 // Stop loading the endpoint. The |completion_callback| will not be called.
138 void StopLoad();
140 private:
141 // Called when when offline initialization succeeds or fails and schedules
142 // |RunCompletionCallback|.
143 void OnOfflineInit(bool success,
144 DriveFirstRunController::UMAOutcome outcome);
146 // Runs |completion_callback|.
147 void RunCompletionCallback(bool success,
148 DriveFirstRunController::UMAOutcome outcome);
150 // content::WebContentsObserver overrides:
151 void DidFailProvisionalLoad(content::RenderFrameHost* render_frame_host,
152 const GURL& validated_url,
153 int error_code,
154 const base::string16& error_description,
155 bool was_ignored_by_handler) override;
157 void DidFailLoad(content::RenderFrameHost* render_frame_host,
158 const GURL& validated_url,
159 int error_code,
160 const base::string16& error_description,
161 bool was_ignored_by_handler) override;
163 // content::WebContentsDelegate overrides:
164 bool ShouldCreateWebContents(
165 content::WebContents* web_contents,
166 int route_id,
167 int main_frame_route_id,
168 WindowContainerType window_container_type,
169 const std::string& frame_name,
170 const GURL& target_url,
171 const std::string& partition_id,
172 content::SessionStorageNamespace* session_storage_namespace) override;
174 // content::NotificationObserver overrides:
175 void Observe(int type,
176 const content::NotificationSource& source,
177 const content::NotificationDetails& details) override;
179 Profile* profile_;
180 const std::string app_id_;
181 const std::string endpoint_url_;
182 scoped_ptr<content::WebContents> web_contents_;
183 content::NotificationRegistrar registrar_;
184 bool started_;
185 CompletionCallback completion_callback_;
186 base::WeakPtrFactory<DriveWebContentsManager> weak_ptr_factory_;
188 DISALLOW_COPY_AND_ASSIGN(DriveWebContentsManager);
191 DriveWebContentsManager::DriveWebContentsManager(
192 Profile* profile,
193 const std::string& app_id,
194 const std::string& endpoint_url,
195 const CompletionCallback& completion_callback)
196 : profile_(profile),
197 app_id_(app_id),
198 endpoint_url_(endpoint_url),
199 started_(false),
200 completion_callback_(completion_callback),
201 weak_ptr_factory_(this) {
202 DCHECK(!completion_callback_.is_null());
203 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
204 content::Source<Profile>(profile_));
207 DriveWebContentsManager::~DriveWebContentsManager() {
210 void DriveWebContentsManager::StartLoad() {
211 started_ = true;
212 const GURL url(endpoint_url_);
213 content::WebContents::CreateParams create_params(
214 profile_, content::SiteInstance::CreateForURL(profile_, url));
216 web_contents_.reset(content::WebContents::Create(create_params));
217 web_contents_->SetDelegate(this);
218 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
219 web_contents_.get());
221 content::NavigationController::LoadURLParams load_params(url);
222 load_params.transition_type = ui::PAGE_TRANSITION_GENERATED;
223 web_contents_->GetController().LoadURLWithParams(load_params);
225 content::WebContentsObserver::Observe(web_contents_.get());
228 void DriveWebContentsManager::StopLoad() {
229 started_ = false;
232 void DriveWebContentsManager::OnOfflineInit(
233 bool success,
234 DriveFirstRunController::UMAOutcome outcome) {
235 if (started_) {
236 // We postpone notifying the controller as we may be in the middle
237 // of a call stack for some routine of the contained WebContents.
238 base::MessageLoop::current()->PostTask(
239 FROM_HERE,
240 base::Bind(&DriveWebContentsManager::RunCompletionCallback,
241 weak_ptr_factory_.GetWeakPtr(),
242 success,
243 outcome));
244 StopLoad();
248 void DriveWebContentsManager::RunCompletionCallback(
249 bool success,
250 DriveFirstRunController::UMAOutcome outcome) {
251 completion_callback_.Run(success, outcome);
254 void DriveWebContentsManager::DidFailProvisionalLoad(
255 content::RenderFrameHost* render_frame_host,
256 const GURL& validated_url,
257 int error_code,
258 const base::string16& error_description,
259 bool was_ignored_by_handler) {
260 if (!render_frame_host->GetParent()) {
261 LOG(WARNING) << "Failed to load WebContents to enable offline mode.";
262 OnOfflineInit(false,
263 DriveFirstRunController::OUTCOME_WEB_CONTENTS_LOAD_FAILED);
267 void DriveWebContentsManager::DidFailLoad(
268 content::RenderFrameHost* render_frame_host,
269 const GURL& validated_url,
270 int error_code,
271 const base::string16& error_description,
272 bool was_ignored_by_handler) {
273 if (!render_frame_host->GetParent()) {
274 LOG(WARNING) << "Failed to load WebContents to enable offline mode.";
275 OnOfflineInit(false,
276 DriveFirstRunController::OUTCOME_WEB_CONTENTS_LOAD_FAILED);
280 bool DriveWebContentsManager::ShouldCreateWebContents(
281 content::WebContents* web_contents,
282 int route_id,
283 int main_frame_route_id,
284 WindowContainerType window_container_type,
285 const std::string& frame_name,
286 const GURL& target_url,
287 const std::string& partition_id,
288 content::SessionStorageNamespace* session_storage_namespace) {
289 if (window_container_type == WINDOW_CONTAINER_TYPE_NORMAL)
290 return true;
292 // Check that the target URL is for the Drive app.
293 const extensions::Extension* extension =
294 extensions::ExtensionRegistry::Get(profile_)
295 ->enabled_extensions().GetAppByURL(target_url);
296 if (!extension || extension->id() != app_id_)
297 return true;
299 // The background contents creation is normally done in Browser, but
300 // because we're using a detached WebContents, we need to do it ourselves.
301 BackgroundContentsService* background_contents_service =
302 BackgroundContentsServiceFactory::GetForProfile(profile_);
304 // Prevent redirection if background contents already exists.
305 if (background_contents_service->GetAppBackgroundContents(
306 base::UTF8ToUTF16(app_id_))) {
307 return false;
309 BackgroundContents* contents = background_contents_service
310 ->CreateBackgroundContents(content::SiteInstance::Create(profile_),
311 route_id,
312 main_frame_route_id,
313 profile_,
314 frame_name,
315 base::ASCIIToUTF16(app_id_),
316 partition_id,
317 session_storage_namespace);
319 contents->web_contents()->GetController().LoadURL(
320 target_url,
321 content::Referrer(),
322 ui::PAGE_TRANSITION_LINK,
323 std::string());
325 // Return false as we already created the WebContents here.
326 return false;
329 void DriveWebContentsManager::Observe(
330 int type,
331 const content::NotificationSource& source,
332 const content::NotificationDetails& details) {
333 if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED) {
334 const std::string app_id = base::UTF16ToUTF8(
335 content::Details<BackgroundContentsOpenedDetails>(details)
336 ->application_id);
337 if (app_id == app_id_)
338 OnOfflineInit(true, DriveFirstRunController::OUTCOME_OFFLINE_ENABLED);
342 ////////////////////////////////////////////////////////////////////////////////
343 // DriveFirstRunController
345 DriveFirstRunController::DriveFirstRunController(Profile* profile)
346 : profile_(profile),
347 started_(false),
348 initial_delay_secs_(kInitialDelaySeconds),
349 web_contents_timeout_secs_(kWebContentsTimeoutSeconds),
350 drive_offline_endpoint_url_(kDriveOfflineEndpointUrl),
351 drive_hosted_app_id_(kDriveHostedAppId) {
354 DriveFirstRunController::~DriveFirstRunController() {
357 void DriveFirstRunController::EnableOfflineMode() {
358 if (!started_) {
359 started_ = true;
360 initial_delay_timer_.Start(
361 FROM_HERE,
362 base::TimeDelta::FromSeconds(initial_delay_secs_),
363 this,
364 &DriveFirstRunController::EnableOfflineMode);
365 return;
368 if (!user_manager::UserManager::Get()->IsLoggedInAsUserWithGaiaAccount()) {
369 LOG(ERROR) << "Attempting to enable offline access "
370 "but not logged in a regular user.";
371 OnOfflineInit(false, OUTCOME_WRONG_USER_TYPE);
372 return;
375 ExtensionService* extension_service =
376 extensions::ExtensionSystem::Get(profile_)->extension_service();
377 if (!extension_service->GetExtensionById(drive_hosted_app_id_, false)) {
378 LOG(WARNING) << "Drive app is not installed.";
379 OnOfflineInit(false, OUTCOME_APP_NOT_INSTALLED);
380 return;
383 BackgroundContentsService* background_contents_service =
384 BackgroundContentsServiceFactory::GetForProfile(profile_);
385 if (background_contents_service->GetAppBackgroundContents(
386 base::UTF8ToUTF16(drive_hosted_app_id_))) {
387 LOG(WARNING) << "Background page for Drive app already exists";
388 OnOfflineInit(false, OUTCOME_BACKGROUND_PAGE_EXISTS);
389 return;
392 web_contents_manager_.reset(new DriveWebContentsManager(
393 profile_,
394 drive_hosted_app_id_,
395 drive_offline_endpoint_url_,
396 base::Bind(&DriveFirstRunController::OnOfflineInit,
397 base::Unretained(this))));
398 web_contents_manager_->StartLoad();
399 web_contents_timer_.Start(
400 FROM_HERE,
401 base::TimeDelta::FromSeconds(web_contents_timeout_secs_),
402 this,
403 &DriveFirstRunController::OnWebContentsTimedOut);
406 void DriveFirstRunController::AddObserver(Observer* observer) {
407 observer_list_.AddObserver(observer);
410 void DriveFirstRunController::RemoveObserver(Observer* observer) {
411 observer_list_.RemoveObserver(observer);
414 void DriveFirstRunController::SetDelaysForTest(int initial_delay_secs,
415 int timeout_secs) {
416 DCHECK(!started_);
417 initial_delay_secs_ = initial_delay_secs;
418 web_contents_timeout_secs_ = timeout_secs;
421 void DriveFirstRunController::SetAppInfoForTest(
422 const std::string& app_id,
423 const std::string& endpoint_url) {
424 DCHECK(!started_);
425 drive_hosted_app_id_ = app_id;
426 drive_offline_endpoint_url_ = endpoint_url;
429 void DriveFirstRunController::OnWebContentsTimedOut() {
430 LOG(WARNING) << "Timed out waiting for web contents.";
431 FOR_EACH_OBSERVER(Observer, observer_list_, OnTimedOut());
432 OnOfflineInit(false, OUTCOME_WEB_CONTENTS_TIMED_OUT);
435 void DriveFirstRunController::CleanUp() {
436 if (web_contents_manager_)
437 web_contents_manager_->StopLoad();
438 web_contents_timer_.Stop();
439 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
442 void DriveFirstRunController::OnOfflineInit(bool success, UMAOutcome outcome) {
443 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
444 if (success)
445 ShowNotification();
446 UMA_HISTOGRAM_ENUMERATION("DriveOffline.CrosAutoEnableOutcome",
447 outcome, OUTCOME_MAX);
448 FOR_EACH_OBSERVER(Observer, observer_list_, OnCompletion(success));
449 CleanUp();
452 void DriveFirstRunController::ShowNotification() {
453 ExtensionService* service =
454 extensions::ExtensionSystem::Get(profile_)->extension_service();
455 DCHECK(service);
456 const extensions::Extension* extension =
457 service->GetExtensionById(drive_hosted_app_id_, false);
458 DCHECK(extension);
460 message_center::RichNotificationData data;
461 data.buttons.push_back(message_center::ButtonInfo(
462 l10n_util::GetStringUTF16(IDS_DRIVE_OFFLINE_NOTIFICATION_BUTTON)));
463 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
464 scoped_ptr<message_center::Notification> notification(
465 new message_center::Notification(
466 message_center::NOTIFICATION_TYPE_SIMPLE, kDriveOfflineNotificationId,
467 base::string16(), // title
468 l10n_util::GetStringUTF16(IDS_DRIVE_OFFLINE_NOTIFICATION_MESSAGE),
469 resource_bundle.GetImageNamed(IDR_NOTIFICATION_DRIVE),
470 base::UTF8ToUTF16(extension->name()), GURL(),
471 message_center::NotifierId(message_center::NotifierId::APPLICATION,
472 kDriveHostedAppId),
473 data, new DriveOfflineNotificationDelegate(profile_)));
474 notification->set_priority(message_center::LOW_PRIORITY);
475 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
478 } // namespace chromeos