diff options
author | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-19 04:10:13 +0000 |
---|---|---|
committer | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-19 04:10:13 +0000 |
commit | c30bda26c225a049e105db1c1164833032c0682f (patch) | |
tree | c50de680ba0da6415d9710ef90dbd8fef3f98501 | |
parent | 494eb70be9ba547f417387fdde551c2ca785904f (diff) | |
download | chromium_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
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 |