summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorxiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-19 04:10:13 +0000
committerxiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-19 04:10:13 +0000
commitc30bda26c225a049e105db1c1164833032c0682f (patch)
treec50de680ba0da6415d9710ef90dbd8fef3f98501
parent494eb70be9ba547f417387fdde551c2ca785904f (diff)
downloadchromium_src-c30bda26c225a049e105db1c1164833032c0682f.zip
chromium_src-c30bda26c225a049e105db1c1164833032c0682f.tar.gz
chromium_src-c30bda26c225a049e105db1c1164833032c0682f.tar.bz2
app_list: Drive app integration.
- DriveAppProvider to map Drive apps to chrome apps or local url apps; - DriveAppMapping to track the mapped chrome apps; - DriveServiceBridge to wrap DriveAPIService and DriveAppRegistry to provide the user Drive apps info; - Put feature behind "--enable-drive-apps-in-app-list"; BUG=358791,345066 TEST=DriveAppProviderTest.*:DriveAppMappingTest.* Review URL: https://codereview.chromium.org/308003005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278265 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/app/generated_resources.grd6
-rw-r--r--chrome/browser/about_flags.cc7
-rw-r--r--chrome/browser/apps/drive/OWNERS8
-rw-r--r--chrome/browser/apps/drive/drive_app_converter.cc (renamed from chrome/browser/ui/app_list/drive/drive_app_converter.cc)48
-rw-r--r--chrome/browser/apps/drive/drive_app_converter.h (renamed from chrome/browser/ui/app_list/drive/drive_app_converter.h)25
-rw-r--r--chrome/browser/apps/drive/drive_app_converter_browsertest.cc (renamed from chrome/browser/ui/app_list/drive/drive_app_converter_browsertest.cc)33
-rw-r--r--chrome/browser/apps/drive/drive_app_mapping.cc127
-rw-r--r--chrome/browser/apps/drive/drive_app_mapping.h47
-rw-r--r--chrome/browser/apps/drive/drive_app_mapping_unittest.cc116
-rw-r--r--chrome/browser/apps/drive/drive_app_provider.cc263
-rw-r--r--chrome/browser/apps/drive/drive_app_provider.h96
-rw-r--r--chrome/browser/apps/drive/drive_app_provider_browsertest.cc453
-rw-r--r--chrome/browser/apps/drive/drive_service_bridge.cc138
-rw-r--r--chrome/browser/apps/drive/drive_service_bridge.h35
-rw-r--r--chrome/browser/drive/fake_drive_service.cc60
-rw-r--r--chrome/browser/drive/fake_drive_service.h13
-rw-r--r--chrome/browser/extensions/crx_installer.cc13
-rw-r--r--chrome/browser/extensions/crx_installer.h33
-rw-r--r--chrome/browser/extensions/crx_installer_browsertest.cc2
-rw-r--r--chrome/browser/extensions/extension_service.cc3
-rw-r--r--chrome/browser/prefs/browser_prefs.cc2
-rw-r--r--chrome/browser/ui/app_list/app_list_syncable_service.cc10
-rw-r--r--chrome/browser/ui/app_list/app_list_syncable_service.h7
-rw-r--r--chrome/browser/ui/app_list/app_list_syncable_service_factory.cc13
-rw-r--r--chrome/chrome_browser_extensions.gypi13
-rw-r--r--chrome/chrome_browser_ui.gypi2
-rw-r--r--chrome/chrome_tests.gypi4
-rw-r--r--chrome/chrome_tests_unit.gypi2
-rw-r--r--chrome/common/pref_names.cc6
-rw-r--r--chrome/common/pref_names.h1
-rw-r--r--chrome/test/data/drive/applist_app_template.json36
-rw-r--r--chrome/test/data/drive/applist_empty.json6
-rw-r--r--ui/app_list/app_list_switches.cc7
-rw-r--r--ui/app_list/app_list_switches.h3
34 files changed, 1567 insertions, 71 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 3b90299..e2d68f1 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6332,6 +6332,12 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_FLAGS_ENABLE_CENTERED_APP_LIST_DESCRIPTION" desc="Description of the flag to center the app launcher and make it wide instead of tall.">
Positions the App Launcher in the center of the screen with a landscape aspect.
</message>
+ <message name="IDS_FLAGS_ENABLE_DRIVE_APPS_IN_APP_LIST_NAME" desc="Name of the flag to enable Drive apps in app launcher.">
+ Enable Drive apps in App Launcher.
+ </message>
+ <message name="IDS_FLAGS_ENABLE_DRIVE_APPS_IN_APP_LIST_DESCRIPTION" desc="Description of the flag to enable Drive apps in app launcher.">
+ Shows Drive apps side by side with Chrome apps in App Launcher.
+ </message>
</if>
<if expr="chromeos">
<message name="IDS_FLAGS_ENABLE_FIRST_RUN_UI_TRANSITIONS_NAME" desc="Name of the flag to enable animated transitions for the first-run tutorial.">
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 7577556..89f854c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1507,6 +1507,13 @@ const Experiment kExperiments[] = {
kOsCrOS,
SINGLE_VALUE_TYPE(app_list::switches::kDisableVoiceSearch)
},
+ {
+ "enable-drive-apps-in-app-list",
+ IDS_FLAGS_ENABLE_DRIVE_APPS_IN_APP_LIST_NAME,
+ IDS_FLAGS_ENABLE_DRIVE_APPS_IN_APP_LIST_DESCRIPTION,
+ kOsDesktop,
+ SINGLE_VALUE_TYPE(app_list::switches::kEnableDriveAppsInAppList)
+ },
#endif
#if defined(OS_ANDROID)
{
diff --git a/chrome/browser/apps/drive/OWNERS b/chrome/browser/apps/drive/OWNERS
new file mode 100644
index 0000000..061c003
--- /dev/null
+++ b/chrome/browser/apps/drive/OWNERS
@@ -0,0 +1,8 @@
+benwells@chromium.org
+calamity@chromium.org
+stevenjb@chromium.org
+tapted@chromium.org
+xiyuan@chromium.org
+
+# Drive API
+kinaba@chromium.org
diff --git a/chrome/browser/ui/app_list/drive/drive_app_converter.cc b/chrome/browser/apps/drive/drive_app_converter.cc
index 096a6f3..592622d 100644
--- a/chrome/browser/ui/app_list/drive/drive_app_converter.cc
+++ b/chrome/browser/apps/drive/drive_app_converter.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/ui/app_list/drive/drive_app_converter.h"
+#include "chrome/browser/apps/drive/drive_app_converter.h"
#include <algorithm>
#include <set>
@@ -101,11 +101,12 @@ class DriveAppConverter::IconFetcher : public net::URLFetcherDelegate,
};
DriveAppConverter::DriveAppConverter(Profile* profile,
- const drive::DriveAppInfo& app_info,
+ const drive::DriveAppInfo& drive_app_info,
const FinishedCallback& finished_callback)
: profile_(profile),
- app_info_(app_info),
- app_(NULL),
+ drive_app_info_(drive_app_info),
+ extension_(NULL),
+ is_new_install_(false),
finished_callback_(finished_callback) {
DCHECK(profile_);
}
@@ -115,28 +116,30 @@ DriveAppConverter::~DriveAppConverter() {
}
void DriveAppConverter::Start() {
- if (app_info_.app_name.empty() ||
- !app_info_.create_url.is_valid()) {
+ DCHECK(!IsStarted());
+
+ if (drive_app_info_.app_name.empty() ||
+ !drive_app_info_.create_url.is_valid()) {
finished_callback_.Run(this, false);
return;
}
- web_app_.title = base::UTF8ToUTF16(app_info_.app_name);
- web_app_.app_url = app_info_.create_url;
+ web_app_.title = base::UTF8ToUTF16(drive_app_info_.app_name);
+ web_app_.app_url = drive_app_info_.create_url;
const std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes,
extension_misc::kExtensionIconSizes +
extension_misc::kNumExtensionIconSizes);
std::set<int> pending_sizes;
- for (size_t i = 0; i < app_info_.app_icons.size(); ++i) {
- const int icon_size = app_info_.app_icons[i].first;
+ for (size_t i = 0; i < drive_app_info_.app_icons.size(); ++i) {
+ const int icon_size = drive_app_info_.app_icons[i].first;
if (allowed_sizes.find(icon_size) == allowed_sizes.end() ||
pending_sizes.find(icon_size) != pending_sizes.end()) {
continue;
}
pending_sizes.insert(icon_size);
- const GURL& icon_url = app_info_.app_icons[i].second;
+ const GURL& icon_url = drive_app_info_.app_icons[i].second;
IconFetcher* fetcher = new IconFetcher(this, icon_url, icon_size);
fetchers_.push_back(fetcher); // Pass ownership to |fetchers|.
fetcher->Start();
@@ -146,6 +149,15 @@ void DriveAppConverter::Start() {
StartInstall();
}
+bool DriveAppConverter::IsStarted() const {
+ return !fetchers_.empty() || crx_installer_;
+}
+
+bool DriveAppConverter::IsInstalling(const std::string& app_id) const {
+ return crx_installer_ && crx_installer_->extension() &&
+ crx_installer_->extension()->id() == app_id;
+}
+
void DriveAppConverter::OnIconFetchComplete(const IconFetcher* fetcher) {
const SkBitmap& icon = fetcher->icon();
if (!icon.empty() && icon.width() != 0) {
@@ -167,6 +179,12 @@ void DriveAppConverter::StartInstall() {
DCHECK(!crx_installer_);
crx_installer_ = extensions::CrxInstaller::CreateSilent(
extensions::ExtensionSystem::Get(profile_)->extension_service());
+ // The converted url app should not be syncable. Drive apps go with the user's
+ // account and url apps will be created when needed. Syncing those apps could
+ // hit an edge case where the synced url apps become orphans when the user has
+ // corresponding chrome apps.
+ crx_installer_->set_do_not_sync(true);
+
extensions::InstallTracker::Get(profile_)->AddObserver(this);
crx_installer_->InstallWebApp(web_app_);
}
@@ -186,8 +204,10 @@ void DriveAppConverter::OnFinishCrxInstall(const std::string& extension_id,
return;
}
- app_ = crx_installer_->extension();
- finished_callback_.Run(this, success);
-
+ extension_ = crx_installer_->extension();
+ is_new_install_ = success && crx_installer_->current_version().empty();
PostInstallCleanUp();
+
+ finished_callback_.Run(this, success);
+ // |finished_callback_| could delete this.
}
diff --git a/chrome/browser/ui/app_list/drive/drive_app_converter.h b/chrome/browser/apps/drive/drive_app_converter.h
index d153c9f..772ea1e 100644
--- a/chrome/browser/ui/app_list/drive/drive_app_converter.h
+++ b/chrome/browser/apps/drive/drive_app_converter.h
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef CHROME_BROWSER_UI_APP_LIST_DRIVE_DRIVE_APP_CONVERTER_H_
-#define CHROME_BROWSER_UI_APP_LIST_DRIVE_DRIVE_APP_CONVERTER_H_
+#ifndef CHROME_BROWSER_APPS_DRIVE_DRIVE_APP_CONVERTER_H_
+#define CHROME_BROWSER_APPS_DRIVE_DRIVE_APP_CONVERTER_H_
#include <string>
@@ -15,13 +15,13 @@
#include "chrome/browser/extensions/install_observer.h"
#include "chrome/common/web_application_info.h"
+class Profile;
+
namespace extensions {
class CrxInstaller;
class Extension;
}
-class Profile;
-
// DriveAppConverter creates and installs a local URL app for the given
// DriveAppInfo into the given profile.
class DriveAppConverter : public extensions::InstallObserver {
@@ -30,14 +30,18 @@ class DriveAppConverter : public extensions::InstallObserver {
FinishedCallback;
DriveAppConverter(Profile* profile,
- const drive::DriveAppInfo& app_info,
+ const drive::DriveAppInfo& drive_app_info,
const FinishedCallback& finished_callback);
virtual ~DriveAppConverter();
void Start();
+ bool IsStarted() const;
+
+ bool IsInstalling(const std::string& app_id) const;
- const drive::DriveAppInfo& app_info() const { return app_info_; }
- const extensions::Extension* app() const { return app_; }
+ const drive::DriveAppInfo& drive_app_info() const { return drive_app_info_; }
+ const extensions::Extension* extension() const { return extension_; }
+ bool is_new_install() const { return is_new_install_; }
private:
class IconFetcher;
@@ -53,10 +57,11 @@ class DriveAppConverter : public extensions::InstallObserver {
bool success) OVERRIDE;
Profile* profile_;
- const drive::DriveAppInfo app_info_;
+ const drive::DriveAppInfo drive_app_info_;
WebApplicationInfo web_app_;
- const extensions::Extension* app_;
+ const extensions::Extension* extension_;
+ bool is_new_install_;
ScopedVector<IconFetcher> fetchers_;
scoped_refptr<extensions::CrxInstaller> crx_installer_;
@@ -66,4 +71,4 @@ class DriveAppConverter : public extensions::InstallObserver {
DISALLOW_COPY_AND_ASSIGN(DriveAppConverter);
};
-#endif // CHROME_BROWSER_UI_APP_LIST_DRIVE_DRIVE_APP_CONVERTER_H_
+#endif // CHROME_BROWSER_APPS_DRIVE_DRIVE_APP_CONVERTER_H_
diff --git a/chrome/browser/ui/app_list/drive/drive_app_converter_browsertest.cc b/chrome/browser/apps/drive/drive_app_converter_browsertest.cc
index 095a51b..ce28ba4 100644
--- a/chrome/browser/ui/app_list/drive/drive_app_converter_browsertest.cc
+++ b/chrome/browser/apps/drive/drive_app_converter_browsertest.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/ui/app_list/drive/drive_app_converter.h"
+#include "chrome/browser/apps/drive/drive_app_converter.h"
#include <utility>
@@ -12,6 +12,7 @@
#include "base/version.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_util.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
@@ -24,6 +25,10 @@
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
+using extensions::AppLaunchInfo;
+using extensions::Extension;
+using extensions::ExtensionRegistry;
+
namespace {
const char kAppName[] = "Test drive app";
@@ -31,7 +36,7 @@ const char kAppUrl[] = "http://foobar.com/drive_app";
} // namespace
-class DriveAppConverterTest : public ExtensionBrowserTest {
+class DriveAppConverterTest : public ExtensionBrowserTest {
public:
DriveAppConverterTest() {}
virtual ~DriveAppConverterTest() {}
@@ -103,47 +108,49 @@ class DriveAppConverterTest : public ExtensionBrowserTest {
IN_PROC_BROWSER_TEST_F(DriveAppConverterTest, GoodApp) {
InstallAndWaitFinish(GetTestDriveApp());
- const extensions::Extension* app = converter()->app();
+ const Extension* app = converter()->extension();
ASSERT_TRUE(app != NULL);
EXPECT_EQ(kAppName, app->name());
EXPECT_TRUE(app->is_hosted_app());
EXPECT_TRUE(app->from_bookmark());
- EXPECT_EQ(GURL(kAppUrl), extensions::AppLaunchInfo::GetLaunchWebURL(app));
+ EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(app));
EXPECT_EQ(extensions::LAUNCH_CONTAINER_TAB,
- extensions::AppLaunchInfo::GetLaunchContainer(app));
+ AppLaunchInfo::GetLaunchContainer(app));
EXPECT_EQ(0u, app->permissions_data()->active_permissions()->apis().size());
EXPECT_EQ(1u, extensions::IconsInfo::GetIcons(app).map().size());
- const extensions::Extension* installed =
- extensions::ExtensionSystem::Get(profile())
- ->extension_service()
- ->GetInstalledExtension(app->id());
+ const Extension* installed = extensions::ExtensionSystem::Get(profile())
+ ->extension_service()
+ ->GetInstalledExtension(app->id());
EXPECT_EQ(app, installed);
+ EXPECT_FALSE(extensions::util::ShouldSyncApp(app, profile()));
}
IN_PROC_BROWSER_TEST_F(DriveAppConverterTest, BadApp) {
drive::DriveAppInfo no_name = GetTestDriveApp();
no_name.app_name.clear();
InstallAndWaitFinish(no_name);
- EXPECT_TRUE(converter()->app() == NULL);
+ EXPECT_TRUE(converter()->extension() == NULL);
drive::DriveAppInfo no_url = GetTestDriveApp();
no_url.create_url = GURL();
InstallAndWaitFinish(no_url);
- EXPECT_TRUE(converter()->app() == NULL);
+ EXPECT_TRUE(converter()->extension() == NULL);
}
IN_PROC_BROWSER_TEST_F(DriveAppConverterTest, InstallTwice) {
InstallAndWaitFinish(GetTestDriveApp());
- const extensions::Extension* first_install = converter()->app();
+ const Extension* first_install = converter()->extension();
ASSERT_TRUE(first_install != NULL);
+ EXPECT_TRUE(converter()->is_new_install());
const std::string first_install_id = first_install->id();
const base::Version first_install_version(first_install->VersionString());
ASSERT_TRUE(first_install_version.IsValid());
InstallAndWaitFinish(GetTestDriveApp());
- const extensions::Extension* second_install = converter()->app();
+ const Extension* second_install = converter()->extension();
ASSERT_TRUE(second_install != NULL);
+ EXPECT_FALSE(converter()->is_new_install());
// Two different app instances.
ASSERT_NE(first_install, second_install);
diff --git a/chrome/browser/apps/drive/drive_app_mapping.cc b/chrome/browser/apps/drive/drive_app_mapping.cc
new file mode 100644
index 0000000..ae95310
--- /dev/null
+++ b/chrome/browser/apps/drive/drive_app_mapping.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 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/apps/drive/drive_app_mapping.h"
+
+#include "base/prefs/pref_service.h"
+#include "base/prefs/scoped_user_pref_update.h"
+#include "base/values.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+
+namespace {
+
+// Key for a string value that holds the mapped chrome app id.
+const char kKeyChromeApp[] = "chrome_app";
+
+// Key for a boolean value of whether the mapped chrome app is auto generated.
+// Default is false.
+const char kKeyGenerated[] = "generated";
+
+scoped_ptr<base::DictionaryValue> CreateInfoDict(
+ const std::string& chrome_app_id,
+ bool generated) {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ dict->SetStringWithoutPathExpansion(kKeyChromeApp, chrome_app_id);
+
+ // Only writes non-default value.
+ if (generated)
+ dict->SetBooleanWithoutPathExpansion(kKeyGenerated, true);
+ return dict.Pass();
+}
+
+} // namespace
+
+DriveAppMapping::DriveAppMapping(PrefService* prefs) : prefs_(prefs) {
+}
+
+DriveAppMapping::~DriveAppMapping() {
+}
+
+// static
+void DriveAppMapping::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterDictionaryPref(
+ prefs::kAppLauncherDriveAppMapping,
+ user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
+}
+
+void DriveAppMapping::Add(const std::string& drive_app_id,
+ const std::string& chrome_app_id,
+ bool generated) {
+ DictionaryPrefUpdate update(prefs_, prefs::kAppLauncherDriveAppMapping);
+ update->SetWithoutPathExpansion(
+ drive_app_id, CreateInfoDict(chrome_app_id, generated).release());
+}
+
+void DriveAppMapping::Remove(const std::string& drive_app_id) {
+ DictionaryPrefUpdate update(prefs_, prefs::kAppLauncherDriveAppMapping);
+ update->RemoveWithoutPathExpansion(drive_app_id, NULL);
+}
+
+std::string DriveAppMapping::GetChromeApp(
+ const std::string& drive_app_id) const {
+ const base::DictionaryValue* mapping =
+ prefs_->GetDictionary(prefs::kAppLauncherDriveAppMapping);
+ const base::DictionaryValue* info_dict;
+ std::string chrome_app_id;
+ if (mapping->GetDictionaryWithoutPathExpansion(drive_app_id, &info_dict) &&
+ info_dict->GetStringWithoutPathExpansion(kKeyChromeApp, &chrome_app_id)) {
+ return chrome_app_id;
+ }
+
+ return std::string();
+}
+
+std::string DriveAppMapping::GetDriveApp(
+ const std::string& chrome_app_id) const {
+ const base::DictionaryValue* mapping =
+ prefs_->GetDictionary(prefs::kAppLauncherDriveAppMapping);
+ for (base::DictionaryValue::Iterator it(*mapping); !it.IsAtEnd();
+ it.Advance()) {
+ const base::DictionaryValue* info_dict;
+ std::string value_string;
+ DCHECK(it.value().IsType(base::Value::TYPE_DICTIONARY));
+ if (it.value().GetAsDictionary(&info_dict) &&
+ info_dict->GetStringWithoutPathExpansion(kKeyChromeApp,
+ &value_string) &&
+ value_string == chrome_app_id) {
+ return it.key();
+ }
+ }
+ return std::string();
+}
+
+bool DriveAppMapping::IsChromeAppGenerated(
+ const std::string& chrome_app_id) const {
+ const base::DictionaryValue* mapping =
+ prefs_->GetDictionary(prefs::kAppLauncherDriveAppMapping);
+ for (base::DictionaryValue::Iterator it(*mapping); !it.IsAtEnd();
+ it.Advance()) {
+ const base::DictionaryValue* info_dict;
+ std::string value_string;
+ bool generated;
+ DCHECK(it.value().IsType(base::Value::TYPE_DICTIONARY));
+ if (it.value().GetAsDictionary(&info_dict) &&
+ info_dict->GetStringWithoutPathExpansion(kKeyChromeApp,
+ &value_string) &&
+ value_string == chrome_app_id &&
+ info_dict->GetBooleanWithoutPathExpansion(kKeyGenerated, &generated)) {
+ return generated;
+ }
+ }
+
+ return false;
+}
+
+std::set<std::string> DriveAppMapping::GetDriveAppIds() const {
+ const base::DictionaryValue* mapping =
+ prefs_->GetDictionary(prefs::kAppLauncherDriveAppMapping);
+ std::set<std::string> keys;
+ for (base::DictionaryValue::Iterator it(*mapping); !it.IsAtEnd();
+ it.Advance()) {
+ keys.insert(it.key());
+ }
+ return keys;
+}
diff --git a/chrome/browser/apps/drive/drive_app_mapping.h b/chrome/browser/apps/drive/drive_app_mapping.h
new file mode 100644
index 0000000..be3c1ee
--- /dev/null
+++ b/chrome/browser/apps/drive/drive_app_mapping.h
@@ -0,0 +1,47 @@
+// Copyright 2014 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_APPS_DRIVE_DRIVE_APP_MAPPING_H_
+#define CHROME_BROWSER_APPS_DRIVE_DRIVE_APP_MAPPING_H_
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class PrefService;
+
+// DriveAppMapping tracks the mapping between Drive apps and corresponding
+// Chrome apps. The data is backed by kAppLauncherDriveAppMapping pref.
+class DriveAppMapping {
+ public:
+ explicit DriveAppMapping(PrefService* prefs);
+ ~DriveAppMapping();
+
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Adds a mapping from |drive_app_id| to |chrome_app_id|. |generated|
+ // represents whether the corresponding Chrome app is generated.
+ void Add(const std::string& drive_app_id,
+ const std::string& chrome_app_id,
+ bool generated);
+ void Remove(const std::string& drive_app_id);
+
+ std::string GetChromeApp(const std::string& drive_app_id) const;
+ std::string GetDriveApp(const std::string& chrome_app_id) const;
+ bool IsChromeAppGenerated(const std::string& chrome_app_id) const;
+
+ std::set<std::string> GetDriveAppIds() const;
+
+ private:
+ PrefService* prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveAppMapping);
+};
+
+#endif // CHROME_BROWSER_APPS_DRIVE_DRIVE_APP_MAPPING_H_
diff --git a/chrome/browser/apps/drive/drive_app_mapping_unittest.cc b/chrome/browser/apps/drive/drive_app_mapping_unittest.cc
new file mode 100644
index 0000000..a1f8949
--- /dev/null
+++ b/chrome/browser/apps/drive/drive_app_mapping_unittest.cc
@@ -0,0 +1,116 @@
+// Copyright 2014 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/apps/drive/drive_app_mapping.h"
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/test/base/testing_pref_service_syncable.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class DriveAppMappingTest : public testing::Test {
+ public:
+ DriveAppMappingTest() {}
+ virtual ~DriveAppMappingTest() {}
+
+ // testing::Test:
+ virtual void SetUp() OVERRIDE {
+ pref_service_.reset(new TestingPrefServiceSyncable);
+ DriveAppMapping::RegisterProfilePrefs(pref_service_->registry());
+
+ mapping_.reset(new DriveAppMapping(pref_service_.get()));
+ }
+
+ DriveAppMapping* mapping() { return mapping_.get(); }
+
+ private:
+ scoped_ptr<TestingPrefServiceSyncable> pref_service_;
+ scoped_ptr<DriveAppMapping> mapping_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveAppMappingTest);
+};
+
+TEST_F(DriveAppMappingTest, Empty) {
+ EXPECT_EQ("", mapping()->GetChromeApp(""));
+ EXPECT_EQ("", mapping()->GetDriveApp(""));
+ EXPECT_EQ("", mapping()->GetChromeApp("non-existent-drive-app"));
+ EXPECT_EQ("", mapping()->GetDriveApp("non-existent-chrome-app"));
+ EXPECT_EQ(0u, mapping()->GetDriveAppIds().size());
+}
+
+TEST_F(DriveAppMappingTest, Add) {
+ std::set<std::string> drive_app_ids;
+
+ // Add one.
+ mapping()->Add("drive", "chrome", false);
+ EXPECT_EQ("chrome", mapping()->GetChromeApp("drive"));
+ EXPECT_EQ("drive", mapping()->GetDriveApp("chrome"));
+ EXPECT_FALSE(mapping()->IsChromeAppGenerated("chrome"));
+ drive_app_ids = mapping()->GetDriveAppIds();
+ EXPECT_EQ(1u, drive_app_ids.size());
+ EXPECT_EQ(1u, drive_app_ids.count("drive"));
+
+ // Overwrite previous mapping if added under the same key.
+ mapping()->Add("drive", "another-chrome-app", true);
+ EXPECT_EQ("", mapping()->GetDriveApp("chrome"));
+ EXPECT_FALSE(mapping()->IsChromeAppGenerated("chrome"));
+ EXPECT_EQ("another-chrome-app", mapping()->GetChromeApp("drive"));
+ EXPECT_EQ("drive", mapping()->GetDriveApp("another-chrome-app"));
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated("another-chrome-app"));
+ drive_app_ids = mapping()->GetDriveAppIds();
+ EXPECT_EQ(1u, drive_app_ids.size());
+ EXPECT_EQ(1u, drive_app_ids.count("drive"));
+
+ // Add another one.
+ mapping()->Add("drive-1", "chrome-1", false);
+ EXPECT_EQ("chrome-1", mapping()->GetChromeApp("drive-1"));
+ EXPECT_EQ("drive-1", mapping()->GetDriveApp("chrome-1"));
+ drive_app_ids = mapping()->GetDriveAppIds();
+ EXPECT_EQ(2u, drive_app_ids.size());
+ EXPECT_EQ(1u, drive_app_ids.count("drive"));
+ EXPECT_EQ(1u, drive_app_ids.count("drive-1"));
+
+ // Previous mapping should not be affected by new mapping.
+ EXPECT_EQ("another-chrome-app", mapping()->GetChromeApp("drive"));
+ EXPECT_EQ("drive", mapping()->GetDriveApp("another-chrome-app"));
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated("another-chrome-app"));
+
+ // Non-existent value returns empty string.
+ EXPECT_EQ("", mapping()->GetChromeApp("non-existent-drive-app"));
+ EXPECT_EQ("", mapping()->GetDriveApp("non-existent-chrome-app"));
+}
+
+TEST_F(DriveAppMappingTest, Remove) {
+ std::set<std::string> drive_app_ids;
+
+ // Prepare data.
+ mapping()->Add("drive-1", "chrome-1", false);
+ mapping()->Add("drive-2", "chrome-2", false);
+ drive_app_ids = mapping()->GetDriveAppIds();
+ EXPECT_EQ(2u, drive_app_ids.size());
+
+ // Remove non-existent.
+ mapping()->Remove("non-existent-drive-app");
+ drive_app_ids = mapping()->GetDriveAppIds();
+ EXPECT_EQ(2u, drive_app_ids.size());
+
+ // Remove one.
+ EXPECT_EQ("chrome-1", mapping()->GetChromeApp("drive-1"));
+ mapping()->Remove("drive-1");
+ EXPECT_EQ("", mapping()->GetChromeApp("drive-1"));
+ drive_app_ids = mapping()->GetDriveAppIds();
+ EXPECT_EQ(1u, drive_app_ids.size());
+
+ // Remove it again has no effect.
+ mapping()->Remove("drive-1");
+ drive_app_ids = mapping()->GetDriveAppIds();
+ EXPECT_EQ(1u, drive_app_ids.size());
+
+ // Remove another one.
+ EXPECT_EQ("chrome-2", mapping()->GetChromeApp("drive-2"));
+ mapping()->Remove("drive-2");
+ EXPECT_EQ("", mapping()->GetChromeApp("drive-2"));
+ drive_app_ids = mapping()->GetDriveAppIds();
+ EXPECT_EQ(0u, drive_app_ids.size());
+}
diff --git a/chrome/browser/apps/drive/drive_app_provider.cc b/chrome/browser/apps/drive/drive_app_provider.cc
new file mode 100644
index 0000000..2bde9fe
--- /dev/null
+++ b/chrome/browser/apps/drive/drive_app_provider.cc
@@ -0,0 +1,263 @@
+// Copyright 2014 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/apps/drive/drive_app_provider.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "chrome/browser/apps/drive/drive_app_converter.h"
+#include "chrome/browser/apps/drive/drive_app_mapping.h"
+#include "chrome/browser/apps/drive/drive_service_bridge.h"
+#include "chrome/browser/drive/drive_app_registry.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/extension.h"
+
+using extensions::Extension;
+using extensions::ExtensionRegistry;
+
+namespace {
+
+void IgnoreUninstallResult(google_apis::GDataErrorCode) {
+}
+
+} // namespace
+
+DriveAppProvider::DriveAppProvider(Profile* profile)
+ : profile_(profile),
+ service_bridge_(DriveServiceBridge::Create(profile).Pass()),
+ mapping_(new DriveAppMapping(profile->GetPrefs())),
+ weak_ptr_factory_(this) {
+ service_bridge_->GetAppRegistry()->AddObserver(this);
+ ExtensionRegistry::Get(profile_)->AddObserver(this);
+}
+
+DriveAppProvider::~DriveAppProvider() {
+ ExtensionRegistry::Get(profile_)->RemoveObserver(this);
+ service_bridge_->GetAppRegistry()->RemoveObserver(this);
+}
+
+// static
+void DriveAppProvider::AppendDependsOnFactories(
+ std::set<BrowserContextKeyedServiceFactory*>* factories) {
+ factories->insert(extensions::ExtensionRegistryFactory::GetInstance());
+ DriveServiceBridge::AppendDependsOnFactories(factories);
+}
+
+void DriveAppProvider::SetDriveServiceBridgeForTest(
+ scoped_ptr<DriveServiceBridge> test_bridge) {
+ service_bridge_->GetAppRegistry()->RemoveObserver(this);
+ service_bridge_ = test_bridge.Pass();
+ service_bridge_->GetAppRegistry()->AddObserver(this);
+}
+
+void DriveAppProvider::UpdateMappingAndExtensionSystem(
+ const std::string& drive_app_id,
+ const Extension* new_app,
+ bool is_new_app_generated) {
+ const std::string& new_chrome_app_id = new_app->id();
+
+ const std::string existing_chrome_app_id =
+ mapping_->GetChromeApp(drive_app_id);
+ if (existing_chrome_app_id == new_chrome_app_id)
+ return;
+
+ const bool is_existing_app_generated =
+ mapping_->IsChromeAppGenerated(existing_chrome_app_id);
+ mapping_->Add(drive_app_id, new_chrome_app_id, is_new_app_generated);
+
+ const Extension* existing_app =
+ ExtensionRegistry::Get(profile_)->GetExtensionById(
+ existing_chrome_app_id, ExtensionRegistry::EVERYTHING);
+ if (existing_app && is_existing_app_generated) {
+ extensions::ExtensionSystem::Get(profile_)
+ ->extension_service()
+ ->UninstallExtension(existing_chrome_app_id, false, NULL);
+ }
+}
+
+void DriveAppProvider::ProcessDeferredOnExtensionInstalled(
+ const std::string drive_app_id,
+ const std::string chrome_app_id) {
+ const Extension* app = ExtensionRegistry::Get(profile_)->GetExtensionById(
+ chrome_app_id, ExtensionRegistry::EVERYTHING);
+ if (!app)
+ return;
+
+ UpdateMappingAndExtensionSystem(drive_app_id, app, false);
+}
+
+void DriveAppProvider::SchedulePendingConverters() {
+ if (pending_converters_.empty())
+ return;
+
+ if (!pending_converters_.front()->IsStarted())
+ pending_converters_.front()->Start();
+}
+
+void DriveAppProvider::OnLocalAppConverted(const DriveAppConverter* converter,
+ bool success) {
+ DCHECK_EQ(pending_converters_.front(), converter);
+
+ if (success) {
+ const bool was_generated =
+ mapping_->GetDriveApp(converter->extension()->id()) ==
+ converter->drive_app_info().app_id &&
+ mapping_->IsChromeAppGenerated(converter->extension()->id());
+ UpdateMappingAndExtensionSystem(
+ converter->drive_app_info().app_id,
+ converter->extension(),
+ converter->is_new_install() || was_generated);
+ } else {
+ LOG(WARNING) << "Failed to convert drive app to web app, "
+ << "drive app id= " << converter->drive_app_info().app_id
+ << ", name=" << converter->drive_app_info().app_name;
+ }
+
+ pending_converters_.erase(pending_converters_.begin());
+ SchedulePendingConverters();
+}
+
+bool DriveAppProvider::IsMappedUrlAppUpToDate(
+ const drive::DriveAppInfo& drive_app) const {
+ const std::string& url_app_id = mapping_->GetChromeApp(drive_app.app_id);
+ if (url_app_id.empty())
+ return false;
+
+ const Extension* url_app = ExtensionRegistry::Get(profile_)->GetExtensionById(
+ url_app_id, ExtensionRegistry::EVERYTHING);
+ if (!url_app)
+ return false;
+ DCHECK(url_app->is_hosted_app() && url_app->from_bookmark());
+
+ return drive_app.app_name == url_app->name() &&
+ drive_app.create_url ==
+ extensions::AppLaunchInfo::GetLaunchWebURL(url_app);
+}
+
+void DriveAppProvider::AddOrUpdateDriveApp(
+ const drive::DriveAppInfo& drive_app) {
+ const Extension* chrome_app =
+ ExtensionRegistry::Get(profile_)->GetExtensionById(
+ drive_app.product_id, ExtensionRegistry::EVERYTHING);
+ if (chrome_app) {
+ UpdateMappingAndExtensionSystem(drive_app.app_id, chrome_app, false);
+ return;
+ }
+
+ if (IsMappedUrlAppUpToDate(drive_app))
+ return;
+
+ ScopedVector<DriveAppConverter>::iterator it = pending_converters_.begin();
+ while (it != pending_converters_.end()) {
+ if (!(*it)->IsStarted() &&
+ (*it)->drive_app_info().app_id == drive_app.app_id) {
+ it = pending_converters_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ pending_converters_.push_back(
+ new DriveAppConverter(profile_,
+ drive_app,
+ base::Bind(&DriveAppProvider::OnLocalAppConverted,
+ base::Unretained(this))));
+}
+
+void DriveAppProvider::ProcessRemovedDriveApp(const std::string& drive_app_id) {
+ const std::string chrome_app_id = mapping_->GetChromeApp(drive_app_id);
+ const bool is_generated = mapping_->IsChromeAppGenerated(chrome_app_id);
+ mapping_->Remove(drive_app_id);
+
+ if (chrome_app_id.empty() || !is_generated)
+ return;
+
+ const Extension* existing_app =
+ ExtensionRegistry::Get(profile_)
+ ->GetExtensionById(chrome_app_id, ExtensionRegistry::EVERYTHING);
+ if (!existing_app)
+ return;
+
+ extensions::ExtensionSystem::Get(profile_)
+ ->extension_service()
+ ->UninstallExtension(chrome_app_id, false, NULL);
+}
+
+void DriveAppProvider::OnDriveAppRegistryUpdated() {
+ service_bridge_->GetAppRegistry()->GetAppList(&drive_apps_);
+
+ IdSet current_ids;
+ for (size_t i = 0; i < drive_apps_.size(); ++i)
+ current_ids.insert(drive_apps_[i].app_id);
+
+ const IdSet existing_ids = mapping_->GetDriveAppIds();
+ const IdSet ids_to_remove =
+ base::STLSetDifference<IdSet>(existing_ids, current_ids);
+ for (IdSet::const_iterator it = ids_to_remove.begin();
+ it != ids_to_remove.end();
+ ++it) {
+ ProcessRemovedDriveApp(*it);
+ }
+
+ for (size_t i = 0; i < drive_apps_.size(); ++i) {
+ AddOrUpdateDriveApp(drive_apps_[i]);
+ }
+ SchedulePendingConverters();
+}
+
+void DriveAppProvider::OnExtensionInstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ // Bail if the |extension| is installed from a converter. The post install
+ // processing will be handled in OnLocalAppConverted.
+ if (!pending_converters_.empty() &&
+ pending_converters_.front()->IsInstalling(extension->id())) {
+ return;
+ }
+
+ // Only user installed app reaches here. If it is mapped, make sure it is not
+ // tagged as generated.
+ const std::string drive_app_id = mapping_->GetDriveApp(extension->id());
+ if (!drive_app_id.empty() &&
+ mapping_->IsChromeAppGenerated(extension->id())) {
+ mapping_->Add(drive_app_id, extension->id(), false);
+ return;
+ }
+
+ for (size_t i = 0; i < drive_apps_.size(); ++i) {
+ if (drive_apps_[i].product_id == extension->id()) {
+ // Defer the processing because it touches the extensions system and
+ // it is better to let the current task finish to avoid unexpected
+ // incomplete status.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&DriveAppProvider::ProcessDeferredOnExtensionInstalled,
+ weak_ptr_factory_.GetWeakPtr(),
+ drive_apps_[i].app_id,
+ extension->id()));
+ return;
+ }
+ }
+}
+
+void DriveAppProvider::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ std::string drive_app_id = mapping_->GetDriveApp(extension->id());
+ if (drive_app_id.empty())
+ return;
+
+ service_bridge_->GetAppRegistry()->UninstallApp(
+ drive_app_id, base::Bind(&IgnoreUninstallResult));
+}
diff --git a/chrome/browser/apps/drive/drive_app_provider.h b/chrome/browser/apps/drive/drive_app_provider.h
new file mode 100644
index 0000000..12a5e21
--- /dev/null
+++ b/chrome/browser/apps/drive/drive_app_provider.h
@@ -0,0 +1,96 @@
+// Copyright 2014 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_APPS_DRIVE_DRIVE_APP_PROVIDER_H_
+#define CHROME_BROWSER_APPS_DRIVE_DRIVE_APP_PROVIDER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/drive/drive_app_registry_observer.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace drive {
+struct DriveAppInfo;
+}
+
+class BrowserContextKeyedServiceFactory;
+class DriveAppConverter;
+class DriveAppMapping;
+class DriveServiceBridge;
+class ExtensionService;
+class Profile;
+
+// DriveAppProvider is the integration point for Drive apps. It ensures each
+// Drive app has a corresponding Chrome app in the extension system. If there
+// is no matching Chrome app, a local URL app would be created. The class
+// processes app changes from both DriveAppRegistry and extension system to
+// keep the two in sync.
+class DriveAppProvider : public drive::DriveAppRegistryObserver,
+ public extensions::ExtensionRegistryObserver {
+ public:
+ explicit DriveAppProvider(Profile* profile);
+ virtual ~DriveAppProvider();
+
+ // Appends PKS factories this class depends on.
+ static void AppendDependsOnFactories(
+ std::set<BrowserContextKeyedServiceFactory*>* factories);
+
+ void SetDriveServiceBridgeForTest(scoped_ptr<DriveServiceBridge> test_bridge);
+
+ private:
+ friend class DriveAppProviderTest;
+
+ typedef std::set<std::string> IdSet;
+ typedef std::vector<drive::DriveAppInfo> DriveAppInfos;
+
+ // Maps |drive_app_id| to |new_app|'s id in mapping. Auto uninstall existing
+ // mapped app if it is an URL app.
+ void UpdateMappingAndExtensionSystem(const std::string& drive_app_id,
+ const extensions::Extension* new_app,
+ bool is_new_app_generated);
+
+ // Deferred processing of relevant extension installed message.
+ void ProcessDeferredOnExtensionInstalled(const std::string drive_app_id,
+ const std::string chrome_app_id);
+
+ void SchedulePendingConverters();
+ void OnLocalAppConverted(const DriveAppConverter* converter, bool success);
+
+ bool IsMappedUrlAppUpToDate(const drive::DriveAppInfo& drive_app) const;
+
+ void AddOrUpdateDriveApp(const drive::DriveAppInfo& drive_app);
+ void ProcessRemovedDriveApp(const std::string& drive_app_id);
+
+ // drive::DriveAppRegistryObserver overrides:
+ virtual void OnDriveAppRegistryUpdated() OVERRIDE;
+
+ // extensions::ExtensionRegistryObserver overrides:
+ virtual void OnExtensionInstalled(
+ content::BrowserContext* browser_context,
+ const extensions::Extension* extension) OVERRIDE;
+ virtual void OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const extensions::Extension* extension) OVERRIDE;
+
+ Profile* profile_;
+
+ scoped_ptr<DriveServiceBridge> service_bridge_;
+ scoped_ptr<DriveAppMapping> mapping_;
+ DriveAppInfos drive_apps_;
+
+ // Tracks the pending web app convertions.
+ ScopedVector<DriveAppConverter> pending_converters_;
+
+ base::WeakPtrFactory<DriveAppProvider> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveAppProvider);
+};
+
+#endif // CHROME_BROWSER_APPS_DRIVE_DRIVE_APP_PROVIDER_H_
diff --git a/chrome/browser/apps/drive/drive_app_provider_browsertest.cc b/chrome/browser/apps/drive/drive_app_provider_browsertest.cc
new file mode 100644
index 0000000..4165a19
--- /dev/null
+++ b/chrome/browser/apps/drive/drive_app_provider_browsertest.cc
@@ -0,0 +1,453 @@
+// Copyright 2014 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/apps/drive/drive_app_provider.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/timer/timer.h"
+#include "chrome/browser/apps/drive/drive_app_mapping.h"
+#include "chrome/browser/apps/drive/drive_service_bridge.h"
+#include "chrome/browser/drive/drive_app_registry.h"
+#include "chrome/browser/drive/fake_drive_service.h"
+#include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/extensions/install_tracker.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "chrome/common/web_application_info.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+
+using extensions::AppLaunchInfo;
+using extensions::Extension;
+using extensions::ExtensionRegistry;
+
+namespace {
+
+const char kDriveAppId[] = "drive_app_id";
+const char kDriveAppName[] = "Fake Drive App";
+const char kLaunchUrl[] = "http://example.com/drive";
+
+// App id of hosted_app.crx.
+const char kChromeAppId[] = "kbmnembihfiondgfjekmnmcbddelicoi";
+
+// Stub drive service bridge.
+class TestDriveServiceBridge : public DriveServiceBridge {
+ public:
+ explicit TestDriveServiceBridge(drive::DriveAppRegistry* registry)
+ : registry_(registry) {}
+ virtual ~TestDriveServiceBridge() {}
+
+ virtual drive::DriveAppRegistry* GetAppRegistry() OVERRIDE {
+ return registry_;
+ }
+
+ private:
+ drive::DriveAppRegistry* registry_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDriveServiceBridge);
+};
+
+} // namespace
+
+class DriveAppProviderTest : public ExtensionBrowserTest,
+ public extensions::InstallObserver {
+ public:
+ DriveAppProviderTest() {}
+ virtual ~DriveAppProviderTest() {}
+
+ // ExtensionBrowserTest:
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ExtensionBrowserTest::SetUpOnMainThread();
+
+ fake_drive_service_.reset(new drive::FakeDriveService);
+ fake_drive_service_->LoadAppListForDriveApi("drive/applist_empty.json");
+ apps_registry_.reset(
+ new drive::DriveAppRegistry(fake_drive_service_.get()));
+
+ provider_.reset(new DriveAppProvider(profile()));
+ provider_->SetDriveServiceBridgeForTest(
+ make_scoped_ptr(new TestDriveServiceBridge(apps_registry_.get()))
+ .PassAs<DriveServiceBridge>());
+ }
+
+ virtual void CleanUpOnMainThread() OVERRIDE {
+ provider_.reset();
+ apps_registry_.reset();
+ fake_drive_service_.reset();
+
+ ExtensionBrowserTest::CleanUpOnMainThread();
+ }
+
+ const Extension* InstallChromeApp(int expected_change) {
+ base::FilePath test_data_path;
+ if (!PathService::Get(chrome::DIR_TEST_DATA, &test_data_path)) {
+ ADD_FAILURE();
+ return NULL;
+ }
+ test_data_path =
+ test_data_path.AppendASCII("extensions").AppendASCII("hosted_app.crx");
+ const Extension* extension =
+ InstallExtension(test_data_path, expected_change);
+ return extension;
+ }
+
+ void RefreshDriveAppRegistry() {
+ apps_registry_->Update();
+ content::RunAllPendingInMessageLoop();
+ }
+
+ void WaitForPendingDriveAppConverters() {
+ DCHECK(!runner_);
+
+ if (provider_->pending_converters_.empty())
+ return;
+
+ runner_ = new content::MessageLoopRunner;
+
+ pending_drive_app_converter_check_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(50),
+ base::Bind(&DriveAppProviderTest::OnPendingDriveAppConverterCheckTimer,
+ base::Unretained(this)));
+
+ runner_->Run();
+
+ pending_drive_app_converter_check_timer_.Stop();
+ runner_ = NULL;
+ }
+
+ void InstallUserUrlApp(const std::string& url) {
+ DCHECK(!runner_);
+ runner_ = new content::MessageLoopRunner;
+
+ WebApplicationInfo web_app;
+ web_app.title = base::ASCIIToUTF16("User installed Url app");
+ web_app.app_url = GURL(url);
+
+ scoped_refptr<extensions::CrxInstaller> crx_installer =
+ extensions::CrxInstaller::CreateSilent(
+ extensions::ExtensionSystem::Get(profile())->extension_service());
+ crx_installer->set_creation_flags(Extension::FROM_BOOKMARK);
+ extensions::InstallTracker::Get(profile())->AddObserver(this);
+ crx_installer->InstallWebApp(web_app);
+
+ runner_->Run();
+ runner_ = NULL;
+ extensions::InstallTracker::Get(profile())->RemoveObserver(this);
+
+ content::RunAllPendingInMessageLoop();
+ }
+
+ bool HasPendingConverters() const {
+ return !provider_->pending_converters_.empty();
+ }
+
+ drive::FakeDriveService* fake_drive_service() {
+ return fake_drive_service_.get();
+ }
+ DriveAppProvider* provider() { return provider_.get(); }
+ DriveAppMapping* mapping() { return provider_->mapping_.get(); }
+
+ private:
+ void OnPendingDriveAppConverterCheckTimer() {
+ if (!HasPendingConverters())
+ runner_->Quit();
+ }
+
+ // extensions::InstallObserver
+ virtual void OnFinishCrxInstall(const std::string& extension_id,
+ bool success) OVERRIDE {
+ runner_->Quit();
+ }
+
+ scoped_ptr<drive::FakeDriveService> fake_drive_service_;
+ scoped_ptr<drive::DriveAppRegistry> apps_registry_;
+ scoped_ptr<DriveAppProvider> provider_;
+
+ base::RepeatingTimer<DriveAppProviderTest>
+ pending_drive_app_converter_check_timer_;
+ scoped_refptr<content::MessageLoopRunner> runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveAppProviderTest);
+};
+
+// A Drive app maps to an existing Chrome app that has a matching id.
+// Uninstalling the chrome app would also disconnect the drive app.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, ExistingChromeApp) {
+ // Prepare an existing chrome app.
+ const Extension* chrome_app = InstallChromeApp(1);
+ ASSERT_TRUE(chrome_app);
+
+ // Prepare a Drive app that matches the chrome app id.
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, chrome_app->id(), kLaunchUrl);
+ RefreshDriveAppRegistry();
+ EXPECT_FALSE(HasPendingConverters());
+
+ // The Drive app should use the matching chrome app.
+ EXPECT_EQ(chrome_app->id(), mapping()->GetChromeApp(kDriveAppId));
+ EXPECT_FALSE(mapping()->IsChromeAppGenerated(chrome_app->id()));
+
+ // Unintalling chrome app should disconnect the Drive app on server.
+ EXPECT_TRUE(fake_drive_service()->HasApp(kDriveAppId));
+ UninstallExtension(chrome_app->id());
+ EXPECT_FALSE(fake_drive_service()->HasApp(kDriveAppId));
+}
+
+// A Drive app creates an URL app when no matching Chrome app presents.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, CreateUrlApp) {
+ // Prepare a Drive app with no underlying chrome app.
+ fake_drive_service()->AddApp(kDriveAppId, kDriveAppName, "", kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ // An Url app should be created.
+ const Extension* chrome_app =
+ ExtensionRegistry::Get(profile())->GetExtensionById(
+ mapping()->GetChromeApp(kDriveAppId), ExtensionRegistry::EVERYTHING);
+ ASSERT_TRUE(chrome_app);
+ EXPECT_EQ(kDriveAppName, chrome_app->name());
+ EXPECT_TRUE(chrome_app->is_hosted_app());
+ EXPECT_TRUE(chrome_app->from_bookmark());
+ EXPECT_EQ(GURL(kLaunchUrl), AppLaunchInfo::GetLaunchWebURL(chrome_app));
+
+ EXPECT_EQ(chrome_app->id(), mapping()->GetChromeApp(kDriveAppId));
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated(chrome_app->id()));
+
+ // Unintalling the chrome app should disconnect the Drive app on server.
+ EXPECT_TRUE(fake_drive_service()->HasApp(kDriveAppId));
+ UninstallExtension(chrome_app->id());
+ EXPECT_FALSE(fake_drive_service()->HasApp(kDriveAppId));
+}
+
+// A matching Chrome app replaces the created URL app.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, MatchingChromeAppInstalled) {
+ // Prepare a Drive app that matches the not-yet-installed kChromeAppId.
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ // An Url app should be created.
+ const Extension* url_app =
+ ExtensionRegistry::Get(profile())->GetExtensionById(
+ mapping()->GetChromeApp(kDriveAppId), ExtensionRegistry::EVERYTHING);
+ EXPECT_TRUE(url_app->is_hosted_app());
+ EXPECT_TRUE(url_app->from_bookmark());
+
+ const std::string url_app_id = url_app->id();
+ EXPECT_NE(kChromeAppId, url_app_id);
+ EXPECT_EQ(url_app_id, mapping()->GetChromeApp(kDriveAppId));
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
+
+ // Installs a chrome app with matching id.
+ InstallChromeApp(0);
+
+ // The Drive app should be mapped to chrome app.
+ EXPECT_EQ(kChromeAppId, mapping()->GetChromeApp(kDriveAppId));
+ EXPECT_FALSE(mapping()->IsChromeAppGenerated(kChromeAppId));
+
+ // Url app should be auto uninstalled.
+ EXPECT_FALSE(ExtensionRegistry::Get(profile())->GetExtensionById(
+ url_app_id, ExtensionRegistry::EVERYTHING));
+}
+
+// Tests that the corresponding URL app is uninstalled when a Drive app is
+// disconnected.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,
+ DisconnectDriveAppUninstallUrlApp) {
+ // Prepare a Drive app that matches the not-yet-installed kChromeAppId.
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ // Url app is created.
+ const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
+ EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
+ url_app_id, ExtensionRegistry::EVERYTHING));
+
+ fake_drive_service()->RemoveAppByProductId(kChromeAppId);
+ RefreshDriveAppRegistry();
+
+ // Url app is auto uninstalled.
+ EXPECT_FALSE(ExtensionRegistry::Get(profile())->GetExtensionById(
+ url_app_id, ExtensionRegistry::EVERYTHING));
+}
+
+// Tests that the matching Chrome app is preserved when a Drive app is
+// disconnected.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,
+ DisconnectDriveAppPreserveChromeApp) {
+ // Prepare an existing chrome app.
+ const Extension* chrome_app = InstallChromeApp(1);
+ ASSERT_TRUE(chrome_app);
+
+ // Prepare a Drive app that matches the chrome app id.
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ EXPECT_FALSE(HasPendingConverters());
+
+ fake_drive_service()->RemoveAppByProductId(kChromeAppId);
+ RefreshDriveAppRegistry();
+
+ // Chrome app is still present after the Drive app is disconnected.
+ EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
+ kChromeAppId, ExtensionRegistry::EVERYTHING));
+}
+
+// The "generated" flag of an app should stay across Drive app conversion.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, KeepGeneratedFlagBetweenUpdates) {
+ // Prepare a Drive app with no underlying chrome app.
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
+
+ // Change name to trigger an update.
+ const char kChangedName[] = "Changed name";
+ fake_drive_service()->RemoveAppByProductId(kChromeAppId);
+ fake_drive_service()->AddApp(
+ kDriveAppId, kChangedName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ // It should still map to the same url app id and tagged as generated.
+ EXPECT_EQ(url_app_id, mapping()->GetChromeApp(kDriveAppId));
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
+}
+
+// A new URL app replaces the existing one and keeps existing// position when a
+// Drive app changes its name or URL.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, DriveAppChanged) {
+ // Prepare a Drive app with no underlying chrome app.
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ // An Url app should be created.
+ const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
+ const Extension* url_app =
+ ExtensionRegistry::Get(profile())
+ ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
+ ASSERT_TRUE(url_app);
+ EXPECT_EQ(kDriveAppName, url_app->name());
+ EXPECT_TRUE(url_app->is_hosted_app());
+ EXPECT_TRUE(url_app->from_bookmark());
+ EXPECT_EQ(GURL(kLaunchUrl), AppLaunchInfo::GetLaunchWebURL(url_app));
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
+
+ // Register the Drive app with a different name and URL.
+ const char kAnotherName[] = "Another drive app name";
+ const char kAnotherLaunchUrl[] = "http://example.com/another_end_point";
+ fake_drive_service()->RemoveAppByProductId(kChromeAppId);
+ fake_drive_service()->AddApp(
+ kDriveAppId, kAnotherName, kChromeAppId, kAnotherLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ // Old URL app should be auto uninstalled.
+ url_app = ExtensionRegistry::Get(profile())
+ ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
+ EXPECT_FALSE(url_app);
+
+ // New URL app should be used.
+ const std::string new_url_app_id = mapping()->GetChromeApp(kDriveAppId);
+ EXPECT_NE(new_url_app_id, url_app_id);
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated(new_url_app_id));
+
+ const Extension* new_url_app =
+ ExtensionRegistry::Get(profile())
+ ->GetExtensionById(new_url_app_id, ExtensionRegistry::EVERYTHING);
+ ASSERT_TRUE(new_url_app);
+ EXPECT_EQ(kAnotherName, new_url_app->name());
+ EXPECT_TRUE(new_url_app->is_hosted_app());
+ EXPECT_TRUE(new_url_app->from_bookmark());
+ EXPECT_EQ(GURL(kAnotherLaunchUrl),
+ AppLaunchInfo::GetLaunchWebURL(new_url_app));
+}
+
+// An existing URL app is not changed when underlying drive app data (name and
+// URL) is not changed.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, NoChange) {
+ // Prepare one Drive app.
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
+ const Extension* url_app =
+ ExtensionRegistry::Get(profile())
+ ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
+
+ // Refresh with no actual change.
+ RefreshDriveAppRegistry();
+ EXPECT_FALSE(HasPendingConverters());
+
+ // Url app should remain unchanged.
+ const std::string new_url_app_id = mapping()->GetChromeApp(kDriveAppId);
+ EXPECT_EQ(new_url_app_id, url_app_id);
+
+ const Extension* new_url_app =
+ ExtensionRegistry::Get(profile())
+ ->GetExtensionById(new_url_app_id, ExtensionRegistry::EVERYTHING);
+ EXPECT_EQ(url_app, new_url_app);
+}
+
+// User installed url app before Drive app conversion should not be tagged
+// as generated and not auto uninstalled.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, UserInstalledBeforeDriveApp) {
+ InstallUserUrlApp(kLaunchUrl);
+
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
+ EXPECT_FALSE(mapping()->IsChromeAppGenerated(url_app_id));
+
+ fake_drive_service()->RemoveAppByProductId(kChromeAppId);
+ RefreshDriveAppRegistry();
+
+ // Url app is still present after the Drive app is disconnected.
+ EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
+ url_app_id, ExtensionRegistry::EVERYTHING));
+}
+
+// Similar to UserInstalledBeforeDriveApp but test the case where user
+// installation happens after Drive app conversion.
+IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, UserInstalledAfterDriveApp) {
+ fake_drive_service()->AddApp(
+ kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
+ RefreshDriveAppRegistry();
+ WaitForPendingDriveAppConverters();
+
+ // Drive app converted and tagged as generated.
+ const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
+ EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
+
+ // User installation resets the generated flag.
+ InstallUserUrlApp(kLaunchUrl);
+ EXPECT_FALSE(mapping()->IsChromeAppGenerated(url_app_id));
+
+ fake_drive_service()->RemoveAppByProductId(kChromeAppId);
+ RefreshDriveAppRegistry();
+
+ // Url app is still present after the Drive app is disconnected.
+ EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
+ url_app_id, ExtensionRegistry::EVERYTHING));
+}
diff --git a/chrome/browser/apps/drive/drive_service_bridge.cc b/chrome/browser/apps/drive/drive_service_bridge.cc
new file mode 100644
index 0000000..207fc35
--- /dev/null
+++ b/chrome/browser/apps/drive/drive_service_bridge.cc
@@ -0,0 +1,138 @@
+// Copyright 2014 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/apps/drive/drive_service_bridge.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "chrome/browser/drive/drive_api_service.h"
+#include "chrome/browser/drive/drive_app_registry.h"
+#include "chrome/browser/drive/drive_notification_manager.h"
+#include "chrome/browser/drive/drive_notification_manager_factory.h"
+#include "chrome/browser/drive/drive_notification_observer.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
+#include "components/signin/core/browser/profile_oauth2_token_service.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace {
+
+// Hosts DriveAPIService and DriveAppRegistry.
+// TODO(xiyuan): Optimize to leverage chromeos::DriveIntegrationService.
+class DriveServiceBridgeImpl : public DriveServiceBridge,
+ public drive::DriveServiceObserver,
+ public drive::DriveNotificationObserver {
+ public:
+ explicit DriveServiceBridgeImpl(Profile* profile);
+ virtual ~DriveServiceBridgeImpl();
+
+ void Initialize();
+
+ // DriveServiceBridge:
+ virtual drive::DriveAppRegistry* GetAppRegistry() OVERRIDE;
+
+ // drive::DriveServiceObserver:
+ virtual void OnReadyToSendRequests() OVERRIDE;
+
+ // drive::DriveNotificationObserver:
+ virtual void OnNotificationReceived() OVERRIDE;
+ virtual void OnPushNotificationEnabled(bool enabled) OVERRIDE;
+
+ private:
+ Profile* profile_;
+ scoped_ptr<drive::DriveServiceInterface> drive_service_;
+ scoped_ptr<drive::DriveAppRegistry> drive_app_registry_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveServiceBridgeImpl);
+};
+
+DriveServiceBridgeImpl::DriveServiceBridgeImpl(Profile* profile)
+ : profile_(profile) {
+ DCHECK(profile_);
+}
+
+DriveServiceBridgeImpl::~DriveServiceBridgeImpl() {
+ drive::DriveNotificationManager* drive_notification_manager =
+ drive::DriveNotificationManagerFactory::FindForBrowserContext(profile_);
+ if (drive_notification_manager)
+ drive_notification_manager->RemoveObserver(this);
+
+ drive_service_->RemoveObserver(this);
+
+ drive_app_registry_.reset();
+ drive_service_.reset();
+}
+
+void DriveServiceBridgeImpl::Initialize() {
+ scoped_refptr<base::SequencedWorkerPool> worker_pool(
+ content::BrowserThread::GetBlockingPool());
+ scoped_refptr<base::SequencedTaskRunner> drive_task_runner(
+ worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
+ worker_pool->GetSequenceToken(),
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN));
+
+ ProfileOAuth2TokenService* token_service =
+ ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
+ drive_service_.reset(new drive::DriveAPIService(
+ token_service,
+ profile_->GetRequestContext(),
+ drive_task_runner.get(),
+ GURL(google_apis::DriveApiUrlGenerator::kBaseUrlForProduction),
+ GURL(google_apis::DriveApiUrlGenerator::kBaseDownloadUrlForProduction),
+ GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction),
+ std::string() /* custom_user_agent */));
+ SigninManagerBase* signin_manager =
+ SigninManagerFactory::GetForProfile(profile_);
+ drive_service_->Initialize(signin_manager->GetAuthenticatedAccountId());
+ drive_service_->AddObserver(this);
+
+ drive::DriveNotificationManager* drive_notification_manager =
+ drive::DriveNotificationManagerFactory::GetForBrowserContext(profile_);
+ if (drive_notification_manager)
+ drive_notification_manager->AddObserver(this);
+
+ drive_app_registry_.reset(new drive::DriveAppRegistry(drive_service_.get()));
+ if (drive_service_->CanSendRequest())
+ drive_app_registry_->Update();
+}
+
+drive::DriveAppRegistry* DriveServiceBridgeImpl::GetAppRegistry() {
+ return drive_app_registry_.get();
+}
+
+void DriveServiceBridgeImpl::OnReadyToSendRequests() {
+ drive_app_registry_->Update();
+}
+
+void DriveServiceBridgeImpl::OnNotificationReceived() {
+ if (drive_service_->CanSendRequest())
+ drive_app_registry_->Update();
+}
+
+void DriveServiceBridgeImpl::OnPushNotificationEnabled(bool enabled) {
+ if (enabled && drive_service_->CanSendRequest())
+ drive_app_registry_->Update();
+}
+
+} // namespace
+
+// static
+scoped_ptr<DriveServiceBridge> DriveServiceBridge::Create(Profile* profile) {
+ scoped_ptr<DriveServiceBridgeImpl> bridge(
+ new DriveServiceBridgeImpl(profile));
+ bridge->Initialize();
+ return bridge.PassAs<DriveServiceBridge>();
+}
+
+// static
+void DriveServiceBridge::AppendDependsOnFactories(
+ std::set<BrowserContextKeyedServiceFactory*>* factories) {
+ DCHECK(factories);
+ factories->insert(ProfileOAuth2TokenServiceFactory::GetInstance());
+ factories->insert(SigninManagerFactory::GetInstance());
+ factories->insert(drive::DriveNotificationManagerFactory::GetInstance());
+}
diff --git a/chrome/browser/apps/drive/drive_service_bridge.h b/chrome/browser/apps/drive/drive_service_bridge.h
new file mode 100644
index 0000000..7bc1ea4
--- /dev/null
+++ b/chrome/browser/apps/drive/drive_service_bridge.h
@@ -0,0 +1,35 @@
+// Copyright 2014 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_APPS_DRIVE_DRIVE_SERVICE_BRIDGE_H_
+#define CHROME_BROWSER_APPS_DRIVE_DRIVE_SERVICE_BRIDGE_H_
+
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace drive {
+class DriveAppRegistry;
+}
+
+class BrowserContextKeyedServiceFactory;
+class Profile;
+
+// An interface to access Drive service for a given profile.
+class DriveServiceBridge {
+ public:
+ virtual ~DriveServiceBridge() {}
+
+ // Factory to create an instance of DriveServiceBridge.
+ static scoped_ptr<DriveServiceBridge> Create(Profile* profile);
+
+ // Appends PKS factories this class depends on.
+ static void AppendDependsOnFactories(
+ std::set<BrowserContextKeyedServiceFactory*>* factories);
+
+ // Returns the DriveAppRegistery to use. The ownership is not transferred.
+ virtual drive::DriveAppRegistry* GetAppRegistry() = 0;
+};
+
+#endif // CHROME_BROWSER_APPS_DRIVE_DRIVE_SERVICE_BRIDGE_H_
diff --git a/chrome/browser/drive/fake_drive_service.cc b/chrome/browser/drive/fake_drive_service.cc
index 37cf960..c69b216 100644
--- a/chrome/browser/drive/fake_drive_service.cc
+++ b/chrome/browser/drive/fake_drive_service.cc
@@ -7,6 +7,7 @@
#include <string>
#include "base/file_util.h"
+#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/md5.h"
#include "base/message_loop/message_loop.h"
@@ -215,6 +216,65 @@ bool FakeDriveService::LoadAppListForDriveApi(
return app_info_value_;
}
+void FakeDriveService::AddApp(const std::string& app_id,
+ const std::string& app_name,
+ const std::string& product_id,
+ const std::string& create_url) {
+ if (app_json_template_.empty()) {
+ base::FilePath path =
+ test_util::GetTestFilePath("drive/applist_app_template.json");
+ CHECK(base::ReadFileToString(path, &app_json_template_));
+ }
+
+ std::string app_json = app_json_template_;
+ ReplaceSubstringsAfterOffset(&app_json, 0, "$AppId", app_id);
+ ReplaceSubstringsAfterOffset(&app_json, 0, "$AppName", app_name);
+ ReplaceSubstringsAfterOffset(&app_json, 0, "$ProductId", product_id);
+ ReplaceSubstringsAfterOffset(&app_json, 0, "$CreateUrl", create_url);
+
+ JSONStringValueSerializer json(app_json);
+ std::string error_message;
+ scoped_ptr<base::Value> value(json.Deserialize(NULL, &error_message));
+ CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+
+ base::ListValue* item_list;
+ CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
+ item_list->Append(value.release());
+}
+
+void FakeDriveService::RemoveAppByProductId(const std::string& product_id) {
+ base::ListValue* item_list;
+ CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
+ for (size_t i = 0; i < item_list->GetSize(); ++i) {
+ base::DictionaryValue* item;
+ CHECK(item_list->GetDictionary(i, &item));
+ const char kKeyProductId[] = "productId";
+ std::string item_product_id;
+ if (item->GetStringWithoutPathExpansion(kKeyProductId, &item_product_id) &&
+ product_id == item_product_id) {
+ item_list->Remove(i, NULL);
+ return;
+ }
+ }
+}
+
+bool FakeDriveService::HasApp(const std::string& app_id) const {
+ base::ListValue* item_list;
+ CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
+ for (size_t i = 0; i < item_list->GetSize(); ++i) {
+ base::DictionaryValue* item;
+ CHECK(item_list->GetDictionary(i, &item));
+ const char kKeyId[] = "id";
+ std::string item_id;
+ if (item->GetStringWithoutPathExpansion(kKeyId, &item_id) &&
+ item_id == app_id) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
void FakeDriveService::SetQuotaValue(int64 used, int64 total) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
diff --git a/chrome/browser/drive/fake_drive_service.h b/chrome/browser/drive/fake_drive_service.h
index f429846..8e296ff 100644
--- a/chrome/browser/drive/fake_drive_service.h
+++ b/chrome/browser/drive/fake_drive_service.h
@@ -34,6 +34,18 @@ class FakeDriveService : public DriveServiceInterface {
// Loads the app list for Drive API. Returns true on success.
bool LoadAppListForDriveApi(const std::string& relative_path);
+ // Adds an app to app list.
+ void AddApp(const std::string& app_id,
+ const std::string& app_name,
+ const std::string& product_id,
+ const std::string& create_url);
+
+ // Removes an app by product id.
+ void RemoveAppByProductId(const std::string& product_id);
+
+ // Returns true if the service knows the given drive app id.
+ bool HasApp(const std::string& app_id) const;
+
// Changes the offline state. All functions fail with GDATA_NO_CONNECTION
// when offline. By default the offline state is false.
void set_offline(bool offline) { offline_ = offline; }
@@ -335,6 +347,7 @@ class FakeDriveService : public DriveServiceInterface {
bool never_return_all_file_list_;
base::FilePath last_cancelled_file_;
GURL share_url_base_;
+ std::string app_json_template_;
DISALLOW_COPY_AND_ASSIGN(FakeDriveService);
};
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index aedc100..7acd8dd 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -229,19 +229,14 @@ void CrxInstaller::InstallWebApp(const WebApplicationInfo& web_app) {
if (!installer_task_runner_->PostTask(
FROM_HERE,
- base::Bind(&CrxInstaller::ConvertWebAppOnFileThread,
- this,
- web_app,
- install_directory_)))
+ base::Bind(&CrxInstaller::ConvertWebAppOnFileThread, this, web_app)))
NOTREACHED();
}
void CrxInstaller::ConvertWebAppOnFileThread(
- const WebApplicationInfo& web_app,
- const base::FilePath& install_directory) {
- base::string16 error;
- scoped_refptr<Extension> extension(
- ConvertWebAppToExtension(web_app, base::Time::Now(), install_directory));
+ const WebApplicationInfo& web_app) {
+ scoped_refptr<Extension> extension(ConvertWebAppToExtension(
+ web_app, base::Time::Now(), install_directory_));
if (!extension.get()) {
// Validation should have stopped any potential errors before getting here.
NOTREACHED() << "Could not convert web app to extension.";
diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h
index fe34b2f..024931d 100644
--- a/chrome/browser/extensions/crx_installer.h
+++ b/chrome/browser/extensions/crx_installer.h
@@ -184,24 +184,13 @@ class CrxInstaller
}
void set_install_immediately(bool val) {
- if (val)
- install_flags_ |= kInstallFlagInstallImmediately;
- else
- install_flags_ &= ~kInstallFlagInstallImmediately;
+ set_install_flag(kInstallFlagInstallImmediately, val);
}
-
void set_is_ephemeral(bool val) {
- if (val)
- install_flags_ |= kInstallFlagIsEphemeral;
- else
- install_flags_ &= ~kInstallFlagIsEphemeral;
+ set_install_flag(kInstallFlagIsEphemeral, val);
}
-
- void set_install_flag(int flag, bool val) {
- if (val)
- install_flags_ |= flag;
- else
- install_flags_ &= ~flag;
+ void set_do_not_sync(bool val) {
+ set_install_flag(kInstallFlagDoNotSync, val);
}
bool did_handle_successfully() const { return did_handle_successfully_; }
@@ -210,6 +199,8 @@ class CrxInstaller
const Extension* extension() { return installer_.extension().get(); }
+ const std::string& current_version() const { return current_version_; }
+
private:
friend class ::ExtensionServiceTest;
friend class ExtensionUpdaterTest;
@@ -224,8 +215,7 @@ class CrxInstaller
void ConvertUserScriptOnFileThread();
// Converts the source web app to an extension.
- void ConvertWebAppOnFileThread(const WebApplicationInfo& web_app,
- const base::FilePath& install_directory);
+ void ConvertWebAppOnFileThread(const WebApplicationInfo& web_app);
// Called after OnUnpackSuccess as a last check to see whether the install
// should complete.
@@ -279,6 +269,13 @@ class CrxInstaller
// and needs additional permissions.
void ConfirmReEnable();
+ void set_install_flag(int flag, bool val) {
+ if (val)
+ install_flags_ |= flag;
+ else
+ install_flags_ &= ~flag;
+ }
+
// The file we're installing.
base::FilePath source_file_;
@@ -286,7 +283,7 @@ class CrxInstaller
GURL download_url_;
// The directory extensions are installed to.
- base::FilePath install_directory_;
+ const base::FilePath install_directory_;
// The location the installation came from (bundled with Chromium, registry,
// manual install, etc). This metadata is saved with the installation if
diff --git a/chrome/browser/extensions/crx_installer_browsertest.cc b/chrome/browser/extensions/crx_installer_browsertest.cc
index 7c583a3..fe4ff28 100644
--- a/chrome/browser/extensions/crx_installer_browsertest.cc
+++ b/chrome/browser/extensions/crx_installer_browsertest.cc
@@ -538,7 +538,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, DoNotSync) {
browser()->profile())->extension_service();
scoped_refptr<CrxInstaller> crx_installer(
CrxInstaller::CreateSilent(service));
- crx_installer->set_install_flag(kInstallFlagDoNotSync, true);
+ crx_installer->set_do_not_sync(true);
crx_installer->InstallCrx(test_data_dir_.AppendASCII("good.crx"));
EXPECT_TRUE(WaitForCrxInstallerDone());
ASSERT_TRUE(crx_installer->extension());
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 8dd3ed6..e5b2679 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -584,8 +584,7 @@ bool ExtensionService::UpdateExtension(const std::string& id,
if (extension) {
installer->set_is_ephemeral(extension_prefs_->IsEphemeralApp(id));
- installer->set_install_flag(extensions::kInstallFlagDoNotSync,
- extension_prefs_->DoNotSync(id));
+ installer->set_do_not_sync(extension_prefs_->DoNotSync(id));
}
installer->set_creation_flags(creation_flags);
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 4c08d99..2e74da7 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -9,6 +9,7 @@
#include "base/prefs/pref_service.h"
#include "chrome/browser/about_flags.h"
#include "chrome/browser/accessibility/invert_bubble_prefs.h"
+#include "chrome/browser/apps/drive/drive_app_mapping.h"
#include "chrome/browser/apps/shortcut_manager.h"
#include "chrome/browser/autocomplete/zero_suggest_provider.h"
#include "chrome/browser/background/background_mode_manager.h"
@@ -419,6 +420,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
autofill::GeneratedCreditCardBubbleController::RegisterUserPrefs(registry);
DeviceIDFetcher::RegisterProfilePrefs(registry);
DevToolsWindow::RegisterProfilePrefs(registry);
+ DriveAppMapping::RegisterProfilePrefs(registry);
extensions::CommandService::RegisterProfilePrefs(registry);
extensions::ExtensionSettingsHandler::RegisterProfilePrefs(registry);
extensions::TabsCaptureVisibleTabFunction::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service.cc b/chrome/browser/ui/app_list/app_list_syncable_service.cc
index 7f552dd..d0d5961 100644
--- a/chrome/browser/ui/app_list/app_list_syncable_service.cc
+++ b/chrome/browser/ui/app_list/app_list_syncable_service.cc
@@ -5,6 +5,7 @@
#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
#include "base/command_line.h"
+#include "chrome/browser/apps/drive/drive_app_provider.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
@@ -241,6 +242,15 @@ void AppListSyncableService::BuildModel() {
VLOG(1) << this << ": AppListSyncableService: InitializeWithProfile.";
apps_builder_->InitializeWithProfile(profile_, model_.get());
}
+
+ if (app_list::switches::IsDriveAppsInAppListEnabled())
+ drive_app_provider_.reset(new DriveAppProvider(profile_));
+}
+
+void AppListSyncableService::Shutdown() {
+ // DriveAppProvider touches other KeyedServices in its dtor and needs be
+ // released in shutdown stage.
+ drive_app_provider_.reset();
}
void AppListSyncableService::Observe(
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service.h b/chrome/browser/ui/app_list/app_list_syncable_service.h
index 951d0d0..7ea8e1f 100644
--- a/chrome/browser/ui/app_list/app_list_syncable_service.h
+++ b/chrome/browser/ui/app_list/app_list_syncable_service.h
@@ -19,6 +19,7 @@
#include "sync/api/syncable_service.h"
#include "sync/protocol/app_list_specifics.pb.h"
+class DriveAppProvider;
class ExtensionAppModelBuilder;
class Profile;
@@ -101,6 +102,9 @@ class AppListSyncableService : public syncer::SyncableService,
class ModelObserver;
typedef std::map<std::string, SyncItem*> SyncItemMap;
+ // KeyedService
+ virtual void Shutdown() OVERRIDE;
+
// content::NotificationObserver
virtual void Observe(int type,
const content::NotificationSource& source,
@@ -196,6 +200,9 @@ class AppListSyncableService : public syncer::SyncableService,
syncer::SyncableService::StartSyncFlare flare_;
std::string oem_folder_name_;
+ // Provides integration with Drive apps.
+ scoped_ptr<DriveAppProvider> drive_app_provider_;
+
DISALLOW_COPY_AND_ASSIGN(AppListSyncableService);
};
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service_factory.cc b/chrome/browser/ui/app_list/app_list_syncable_service_factory.cc
index 09a6a80..3ef9d92 100644
--- a/chrome/browser/ui/app_list/app_list_syncable_service_factory.cc
+++ b/chrome/browser/ui/app_list/app_list_syncable_service_factory.cc
@@ -4,7 +4,10 @@
#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
+#include <set>
+
#include "base/prefs/pref_service.h"
+#include "chrome/browser/apps/drive/drive_app_provider.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
@@ -50,8 +53,16 @@ AppListSyncableServiceFactory::AppListSyncableServiceFactory()
"AppListSyncableService",
BrowserContextDependencyManager::GetInstance()) {
VLOG(1) << "AppListSyncableServiceFactory()";
- DependsOn(
+ typedef std::set<BrowserContextKeyedServiceFactory*> FactorySet;
+ FactorySet dependent_factories;
+ dependent_factories.insert(
extensions::ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+ DriveAppProvider::AppendDependsOnFactories(&dependent_factories);
+ for (FactorySet::iterator it = dependent_factories.begin();
+ it != dependent_factories.end();
+ ++it) {
+ DependsOn(*it);
+ }
}
AppListSyncableServiceFactory::~AppListSyncableServiceFactory() {
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 7d8337d..795e7ae 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -78,6 +78,14 @@
'browser/apps/app_url_redirector.h',
'browser/apps/chrome_apps_client.cc',
'browser/apps/chrome_apps_client.h',
+ 'browser/apps/drive/drive_app_converter.cc',
+ 'browser/apps/drive/drive_app_converter.h',
+ 'browser/apps/drive/drive_app_mapping.cc',
+ 'browser/apps/drive/drive_app_mapping.h',
+ 'browser/apps/drive/drive_app_provider.cc',
+ 'browser/apps/drive/drive_app_provider.h',
+ 'browser/apps/drive/drive_service_bridge.cc',
+ 'browser/apps/drive/drive_service_bridge.h',
'browser/apps/ephemeral_app_launcher.cc',
'browser/apps/ephemeral_app_launcher.h',
'browser/apps/ephemeral_app_service.cc',
@@ -1251,6 +1259,11 @@
'browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api_stub.cc',
],
}],
+ ['enable_app_list==0', {
+ 'sources/': [
+ ['exclude', '^browser/apps/drive/'],
+ ],
+ }]
],
},
{
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index 26689bc..7fafa6b 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -171,8 +171,6 @@
'browser/ui/app_list/app_list_util.h',
'browser/ui/app_list/app_list_view_delegate.cc',
'browser/ui/app_list/app_list_view_delegate.h',
- 'browser/ui/app_list/drive/drive_app_converter.cc',
- 'browser/ui/app_list/drive/drive_app_converter.h',
'browser/ui/app_list/extension_app_item.cc',
'browser/ui/app_list/extension_app_item.h',
'browser/ui/app_list/extension_app_model_builder.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index efb4a38..93c0573 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -879,6 +879,8 @@
'browser/apps/app_crash_browsertest.cc',
'browser/apps/app_window_browsertest.cc',
'browser/apps/app_url_redirector_browsertest.cc',
+ 'browser/apps/drive/drive_app_converter_browsertest.cc',
+ 'browser/apps/drive/drive_app_provider_browsertest.cc',
'browser/apps/ephemeral_app_browsertest.cc',
'browser/apps/ephemeral_app_browsertest.h',
'browser/apps/ephemeral_app_service_browsertest.cc',
@@ -1365,7 +1367,6 @@
'browser/translate/translate_manager_browsertest.cc',
'browser/ui/app_list/app_list_controller_browsertest.cc',
'browser/ui/app_list/app_list_service_views_browsertest.cc',
- 'browser/ui/app_list/drive/drive_app_converter_browsertest.cc',
'browser/ui/app_list/search/people/people_provider_browsertest.cc',
'browser/ui/app_list/search/webstore/webstore_provider_browsertest.cc',
'browser/ui/ash/accelerator_commands_browsertest.cc',
@@ -1979,6 +1980,7 @@
}],
['enable_app_list==0', {
'sources/': [
+ ['exclude', '^browser/apps/drive/'],
['exclude', '^browser/ui/app_list/'],
['exclude', '^browser/ui/webui/app_list/'],
],
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 2eac850..311831f 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -599,6 +599,7 @@
'browser/android/mock_google_location_settings_helper.cc',
'browser/android/mock_google_location_settings_helper.h',
'browser/app_controller_mac_unittest.mm',
+ 'browser/apps/drive/drive_app_mapping_unittest.cc',
'browser/apps/ephemeral_app_service_unittest.cc',
'browser/autocomplete/autocomplete_input_unittest.cc',
'browser/autocomplete/autocomplete_match_unittest.cc',
@@ -2738,6 +2739,7 @@
],
}, {
'sources/': [
+ ['exclude', '^browser/apps/drive/'],
['exclude', '^browser/ui/app_list/'],
['exclude', '^browser/ui/views/app_list/'],
],
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 6d50a5f..0b48677 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2341,6 +2341,12 @@ const char kAppLauncherShortcutVersion[] = "apps.app_launcher.shortcut_version";
// A boolean identifying if we should show the app launcher promo or not.
const char kShowAppLauncherPromo[] = "app_launcher.show_promo";
+
+// A dictionary that tracks the Drive app to Chrome app mapping. The key is
+// a Drive app id and the value is the corresponding Chrome app id. The pref
+// is unsynable and used to track local mappings only.
+const char kAppLauncherDriveAppMapping[] =
+ "apps.app_launcher.drive_app_mapping";
#endif
// If set, the user requested to launch the app with this extension id while
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 0fa6de9..6c1ea9c7 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -812,6 +812,7 @@ extern const char kAppListEnableTime[];
extern const char kAppLauncherIsEnabled[];
extern const char kAppLauncherShortcutVersion[];
extern const char kShowAppLauncherPromo[];
+extern const char kAppLauncherDriveAppMapping[];
#endif
extern const char kAppLaunchForMetroRestart[];
diff --git a/chrome/test/data/drive/applist_app_template.json b/chrome/test/data/drive/applist_app_template.json
new file mode 100644
index 0000000..3ce970b
--- /dev/null
+++ b/chrome/test/data/drive/applist_app_template.json
@@ -0,0 +1,36 @@
+{
+ "kind": "drive#app",
+ "id": "$AppId",
+ "name": "$AppName",
+ "objectType": "",
+ "supportsCreate": false,
+ "supportsImport": false,
+ "installed": true,
+ "authorized": false,
+ "removable": false,
+ "productUrl": "https://chrome.google.com/webstore/detail/$ProductId",
+ "productId": "$ProductId",
+ "createUrl": "$CreateUrl",
+ "primaryMimeTypes": [
+ "image/jpeg",
+ "image/png",
+ "application/vnd.google-apps.drive-sdk.876543210000"
+ ],
+ "icons": [
+ {
+ "category": "application",
+ "size": 10,
+ "iconUrl": "http://www.example.com/10.png"
+ },
+ {
+ "category": "document",
+ "size": 10,
+ "iconUrl": "http://www.example.com/d10.png"
+ },
+ {
+ "category": "documentShared",
+ "size": 10,
+ "iconUrl": "http://www.example.com/ds10.png"
+ }
+ ]
+}
diff --git a/chrome/test/data/drive/applist_empty.json b/chrome/test/data/drive/applist_empty.json
new file mode 100644
index 0000000..2dcd51b
--- /dev/null
+++ b/chrome/test/data/drive/applist_empty.json
@@ -0,0 +1,6 @@
+{
+ "kind": "drive#appList",
+ "etag": "\"Jm4BaSnCWNND-noZsHINRqj4ABC/tuqRBw0lvjUdPtc_2msA1tN4XYZ\"",
+ "selfLink": "https://www.googleapis.com/drive/v2/apps",
+ "items": []
+}
diff --git a/ui/app_list/app_list_switches.cc b/ui/app_list/app_list_switches.cc
index 31ec25b..4e9ebf3 100644
--- a/ui/app_list/app_list_switches.cc
+++ b/ui/app_list/app_list_switches.cc
@@ -21,6 +21,9 @@ const char kDisableVoiceSearch[] = "disable-app-list-voice-search";
// If set, the app list will be centered and wide instead of tall.
const char kEnableCenteredAppList[] = "enable-centered-app-list";
+// If set, Drive apps of the user shows side-by-side with Chrome apps.
+const char kEnableDriveAppsInAppList[] = "enable-drive-apps-in-app-list";
+
// If set, the experimental app list will be used. Implies
// --enable-centered-app-list.
const char kEnableExperimentalAppList[] = "enable-experimental-app-list";
@@ -72,5 +75,9 @@ bool IsCenteredAppListEnabled() {
IsExperimentalAppListEnabled();
}
+bool IsDriveAppsInAppListEnabled() {
+ return CommandLine::ForCurrentProcess()->HasSwitch(kEnableDriveAppsInAppList);
+}
+
} // namespace switches
} // namespace app_list
diff --git a/ui/app_list/app_list_switches.h b/ui/app_list/app_list_switches.h
index 42c2342..cb39ad6 100644
--- a/ui/app_list/app_list_switches.h
+++ b/ui/app_list/app_list_switches.h
@@ -15,6 +15,7 @@ APP_LIST_EXPORT extern const char kDisableAppInfo[];
APP_LIST_EXPORT extern const char kDisableSyncAppList[];
APP_LIST_EXPORT extern const char kDisableVoiceSearch[];
APP_LIST_EXPORT extern const char kEnableCenteredAppList[];
+APP_LIST_EXPORT extern const char kEnableDriveAppsInAppList[];
APP_LIST_EXPORT extern const char kEnableExperimentalAppList[];
APP_LIST_EXPORT extern const char kEnableHotwordAlwaysOn[];
APP_LIST_EXPORT extern const char kEnableSyncAppList[];
@@ -35,6 +36,8 @@ bool APP_LIST_EXPORT IsExperimentalAppListEnabled();
// the conditions that trigger the position.
bool APP_LIST_EXPORT IsCenteredAppListEnabled();
+bool APP_LIST_EXPORT IsDriveAppsInAppListEnabled();
+
} // namespace switches
} // namespace app_list