Commit 65853274 authored by tengs@chromium.org's avatar tengs@chromium.org

Add flow to enable Google Drive offline mode automatically on first run.

How this works is that we create a WebContents in the background to a Drive
endpoint that will take care of all the offline initialization for the app.
We succeed if a background page is opened; otherwise, we fail if the page does
not load or after a timeout.

BUG=307302
TEST=manual

Review URL: https://codereview.chromium.org/55423004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@233501 0039d316-1c4b-4281-b951-d872f2087c98
parent bbce7be6
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/first_run/drive_first_run_controller.h"
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/background/background_contents_service.h"
#include "chrome/browser/background/background_contents_service_factory.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/tab_contents/background_contents.h"
#include "chrome/common/extensions/extension.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "url/gurl.h"
namespace chromeos {
namespace {
// The initial time to wait in seconds before starting the opt-in.
const int kInitialDelaySeconds = 180;
// Time to wait for Drive app background page to come up before giving up.
const int kWebContentsTimeoutSeconds = 15;
// Google Drive offline opt-in endpoint.
const char kDriveOfflineEndpointUrl[] = "http://drive.google.com";
// Google Drive app id.
const char kDriveHostedAppId[] = "apdfllckaahabafndbhieahigkjlhalf";
} // namespace
////////////////////////////////////////////////////////////////////////////////
// DriveWebContentsManager
// Manages web contents that does Google Drive offline opt-in. We create
// a background WebContents that loads a Drive endpoint to initialize offline
// mode. If successful, a background page will be opened to sync the user's
// files for offline use.
class DriveWebContentsManager : public content::WebContentsObserver,
public content::WebContentsDelegate,
public content::NotificationObserver {
public:
typedef base::Callback<void(bool)> CompletionCallback;
DriveWebContentsManager(Profile* profile,
const CompletionCallback& completion_callback);
virtual ~DriveWebContentsManager();
// Start loading the WebContents for the endpoint in the context of the Drive
// hosted app that will initialize offline mode and open a background page.
void StartLoad();
// Stop loading the endpoint. The |completion_callback| will not be called.
void StopLoad();
private:
// Called when when offline initialization succeeds or fails and schedules
// |RunCompletionCallback|.
void OnOfflineInit(bool success);
// Runs |completion_callback|.
void RunCompletionCallback(bool success);
// content::WebContentsObserver overrides:
virtual void DidFailProvisionalLoad(
int64 frame_id,
const string16& frame_unique_name,
bool is_main_frame,
const GURL& validated_url,
int error_code,
const string16& error_description,
content::RenderViewHost* render_view_host) OVERRIDE;
virtual void DidFailLoad(int64 frame_id,
const GURL& validated_url,
bool is_main_frame,
int error_code,
const string16& error_description,
content::RenderViewHost* render_view_host) OVERRIDE;
// content::WebContentsDelegate overrides:
virtual bool ShouldCreateWebContents(
content::WebContents* web_contents,
int route_id,
WindowContainerType window_container_type,
const string16& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) OVERRIDE;
// content::NotificationObserver overrides:
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
Profile* profile_;
scoped_ptr<content::WebContents> web_contents_;
content::NotificationRegistrar registrar_;
bool started_;
CompletionCallback completion_callback_;
base::WeakPtrFactory<DriveWebContentsManager> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(DriveWebContentsManager);
};
DriveWebContentsManager::DriveWebContentsManager(
Profile* profile,
const CompletionCallback& completion_callback)
: profile_(profile),
started_(false),
completion_callback_(completion_callback),
weak_ptr_factory_(this) {
DCHECK(!completion_callback_.is_null());
registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
content::Source<Profile>(profile_));
}
DriveWebContentsManager::~DriveWebContentsManager() {
}
void DriveWebContentsManager::StartLoad() {
started_ = true;
const GURL url(kDriveOfflineEndpointUrl);
content::WebContents::CreateParams create_params(
profile_, content::SiteInstance::CreateForURL(profile_, url));
web_contents_.reset(content::WebContents::Create(create_params));
web_contents_->SetDelegate(this);
content::NavigationController::LoadURLParams load_params(url);
load_params.transition_type = content::PAGE_TRANSITION_GENERATED;
web_contents_->GetController().LoadURLWithParams(load_params);
content::WebContentsObserver::Observe(web_contents_.get());
}
void DriveWebContentsManager::StopLoad() {
started_ = false;
}
void DriveWebContentsManager::OnOfflineInit(bool success) {
if (started_) {
// We postpone notifying the controller as we may be in the middle
// of a call stack for some routine of the contained WebContents.
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&DriveWebContentsManager::RunCompletionCallback,
weak_ptr_factory_.GetWeakPtr(),
success));
StopLoad();
}
}
void DriveWebContentsManager::RunCompletionCallback(bool success) {
completion_callback_.Run(success);
}
void DriveWebContentsManager::DidFailProvisionalLoad(
int64 frame_id,
const string16& frame_unique_name,
bool is_main_frame,
const GURL& validated_url,
int error_code,
const string16& error_description,
content::RenderViewHost* render_view_host) {
OnOfflineInit(false);
}
void DriveWebContentsManager::DidFailLoad(
int64 frame_id,
const GURL& validated_url,
bool is_main_frame,
int error_code,
const string16& error_description,
content::RenderViewHost* render_view_host) {
OnOfflineInit(false);
}
bool DriveWebContentsManager::ShouldCreateWebContents(
content::WebContents* web_contents,
int route_id,
WindowContainerType window_container_type,
const string16& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) {
if (window_container_type == WINDOW_CONTAINER_TYPE_NORMAL)
return true;
// Check that the target URL is for the Drive app.
ExtensionService* service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
const extensions::Extension *extension =
service->GetInstalledApp(target_url);
if (!extension || extension->id() != kDriveHostedAppId)
return true;
// The background contents creation is normally done in Browser, but
// because we're using a detached WebContents, we need to do it ourselves.
BackgroundContentsService* background_contents_service =
BackgroundContentsServiceFactory::GetForProfile(profile_);
// Prevent redirection if background contents already exists.
if (background_contents_service->GetAppBackgroundContents(
UTF8ToUTF16(kDriveHostedAppId))) {
return false;
}
BackgroundContents* contents = background_contents_service
->CreateBackgroundContents(content::SiteInstance::Create(profile_),
route_id,
profile_,
frame_name,
ASCIIToUTF16(kDriveHostedAppId),
partition_id,
session_storage_namespace);
contents->web_contents()->GetController().LoadURL(
target_url,
content::Referrer(),
content::PAGE_TRANSITION_LINK,
std::string());
// Return false as we already created the WebContents here.
return false;
}
void DriveWebContentsManager::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED) {
const std::string app_id = UTF16ToUTF8(
content::Details<BackgroundContentsOpenedDetails>(details)
->application_id);
if (app_id == kDriveHostedAppId)
OnOfflineInit(true);
}
}
////////////////////////////////////////////////////////////////////////////////
// DriveFirstRunController
DriveFirstRunController::DriveFirstRunController()
: profile_(ProfileManager::GetDefaultProfile()),
started_(false) {
}
DriveFirstRunController::~DriveFirstRunController() {
}
void DriveFirstRunController::EnableOfflineMode() {
if (!started_) {
started_ = true;
initial_delay_timer_.Start(
FROM_HERE,
base::TimeDelta::FromSeconds(kInitialDelaySeconds),
this,
&DriveFirstRunController::EnableOfflineMode);
}
if (!UserManager::Get()->IsLoggedInAsRegularUser()) {
LOG(ERROR) << "Attempting to enable offline access "
"but not logged in a regular user.";
OnOfflineInit(false);
return;
}
ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
if (!extension_service->GetExtensionById(kDriveHostedAppId, false)) {
LOG(WARNING) << "Drive app is not installed.";
OnOfflineInit(false);
return;
}
BackgroundContentsService* background_contents_service =
BackgroundContentsServiceFactory::GetForProfile(profile_);
if (background_contents_service->GetAppBackgroundContents(
UTF8ToUTF16(kDriveHostedAppId))) {
LOG(WARNING) << "Background page for Drive app already exists";
OnOfflineInit(false);
return;
}
web_contents_manager_.reset(new DriveWebContentsManager(
profile_,
base::Bind(&DriveFirstRunController::OnOfflineInit,
base::Unretained(this))));
web_contents_manager_->StartLoad();
web_contents_timer_.Start(
FROM_HERE,
base::TimeDelta::FromSeconds(kWebContentsTimeoutSeconds),
this,
&DriveFirstRunController::OnWebContentsTimedOut);
}
void DriveFirstRunController::OnWebContentsTimedOut() {
LOG(WARNING) << "Timed out waiting for web contents to opt-in";
OnOfflineInit(false);
}
void DriveFirstRunController::CleanUp() {
if (web_contents_manager_)
web_contents_manager_->StopLoad();
web_contents_timer_.Stop();
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
void DriveFirstRunController::OnOfflineInit(bool success) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// TODO(tengs): Notify ash system tray to display notice that Drive
// offline opt-in has occurred. See crbug.com/311452.
CleanUp();
}
} // namespace chromeos
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_FIRST_RUN_DRIVE_FIRST_RUN_CONTROLLER_H_
#define CHROME_BROWSER_CHROMEOS_FIRST_RUN_DRIVE_FIRST_RUN_CONTROLLER_H_
#include "base/basictypes.h"
#include "base/timer/timer.h"
#include "chrome/browser/profiles/profile.h"
namespace chromeos {
class DriveWebContentsManager;
// This class is responsible for kicking off the Google Drive offline
// initialization process. There is an initial delay to avoid contention when
// the session starts. DriveFirstRunController will manage its own lifetime and
// destroy itself when the initialization succeeds or fails.
class DriveFirstRunController {
public:
DriveFirstRunController();
~DriveFirstRunController();
// Starts the process to enable offline mode for the user's Drive account.
void EnableOfflineMode();
private:
// Used as a callback to indicate whether the offline initialization
// succeeds or fails.
void OnOfflineInit(bool success);
// Called when timed out waiting for offline initialization to complete.
void OnWebContentsTimedOut();
// Cleans up internal state and schedules self for deletion.
void CleanUp();
Profile* profile_;
scoped_ptr<DriveWebContentsManager> web_contents_manager_;
base::OneShotTimer<DriveFirstRunController> web_contents_timer_;
base::OneShotTimer<DriveFirstRunController> initial_delay_timer_;
bool started_;
DISALLOW_COPY_AND_ASSIGN(DriveFirstRunController);
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_FIRST_RUN_DRIVE_FIRST_RUN_CONTROLLER_H_
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h" #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
#include "chrome/browser/chromeos/customization_document.h" #include "chrome/browser/chromeos/customization_document.h"
#include "chrome/browser/chromeos/first_run/drive_first_run_controller.h"
#include "chrome/browser/chromeos/first_run/first_run_controller.h" #include "chrome/browser/chromeos/first_run/first_run_controller.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h" #include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/kiosk_mode/kiosk_mode_settings.h" #include "chrome/browser/chromeos/kiosk_mode/kiosk_mode_settings.h"
...@@ -318,6 +319,16 @@ LoginDisplayHostImpl::~LoginDisplayHostImpl() { ...@@ -318,6 +319,16 @@ LoginDisplayHostImpl::~LoginDisplayHostImpl() {
// completion. // completion.
(new FirstRunController())->Start(); (new FirstRunController())->Start();
} }
// TODO(tengs): This should be refactored together with the first run UI.
// See crbug.com/314934.
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableDriveOfflineFirstRun)) {
if (UserManager::Get()->IsCurrentUserNew()) {
// DriveOptInController will delete itself when finished.
(new DriveFirstRunController())->EnableOfflineMode();
}
}
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
......
...@@ -366,6 +366,8 @@ ...@@ -366,6 +366,8 @@
'browser/chromeos/fileapi/file_system_backend.cc', 'browser/chromeos/fileapi/file_system_backend.cc',
'browser/chromeos/fileapi/file_system_backend.h', 'browser/chromeos/fileapi/file_system_backend.h',
'browser/chromeos/fileapi/file_system_backend_delegate.h', 'browser/chromeos/fileapi/file_system_backend_delegate.h',
'browser/chromeos/first_run/drive_first_run_controller.cc',
'browser/chromeos/first_run/drive_first_run_controller.h',
'browser/chromeos/first_run/first_run_controller.cc', 'browser/chromeos/first_run/first_run_controller.cc',
'browser/chromeos/first_run/first_run_controller.h', 'browser/chromeos/first_run/first_run_controller.h',
'browser/chromeos/first_run/first_run_view.cc', 'browser/chromeos/first_run/first_run_view.cc',
......
...@@ -82,6 +82,9 @@ const char kEnableCarrierSwitching[] = "enable-carrier-switching"; ...@@ -82,6 +82,9 @@ const char kEnableCarrierSwitching[] = "enable-carrier-switching";
const char kEnableChromeCaptivePortalDetector[] = const char kEnableChromeCaptivePortalDetector[] =
"enable-chrome-captive-portal-detector"; "enable-chrome-captive-portal-detector";
// Enables automatically initializing Google Drive offline mode on first run.
const char kEnableDriveOfflineFirstRun[] = "enable-drive-offline-first-run";
// Enables reporting recently logged in users for enterprise-managed devices. // Enables reporting recently logged in users for enterprise-managed devices.
const char kEnableEnterpriseUserReporting[] = const char kEnableEnterpriseUserReporting[] =
"enable-enterprise-user-reporting"; "enable-enterprise-user-reporting";
......
...@@ -41,6 +41,7 @@ CHROMEOS_EXPORT extern const char kEchoExtensionPath[]; ...@@ -41,6 +41,7 @@ CHROMEOS_EXPORT extern const char kEchoExtensionPath[];
CHROMEOS_EXPORT extern const char kEnableBackgroundLoader[]; CHROMEOS_EXPORT extern const char kEnableBackgroundLoader[];
CHROMEOS_EXPORT extern const char kEnableCarrierSwitching[]; CHROMEOS_EXPORT extern const char kEnableCarrierSwitching[];
CHROMEOS_EXPORT extern const char kEnableChromeCaptivePortalDetector[]; CHROMEOS_EXPORT extern const char kEnableChromeCaptivePortalDetector[];
CHROMEOS_EXPORT extern const char kEnableDriveOfflineFirstRun[];
CHROMEOS_EXPORT extern const char kEnableEnterpriseUserReporting[]; CHROMEOS_EXPORT extern const char kEnableEnterpriseUserReporting[];
CHROMEOS_EXPORT extern const char kEnableIMEModeIndicator[]; CHROMEOS_EXPORT extern const char kEnableIMEModeIndicator[];
CHROMEOS_EXPORT extern const char kEnableKioskMode[]; CHROMEOS_EXPORT extern const char kEnableKioskMode[];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment