Commit dafeaa3d authored by xiyuan@chromium.org's avatar xiyuan@chromium.org

app_list: DriveAppConverter to install local URL app from DriveAppInfo.

BUG=358808

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@271355 0039d316-1c4b-4281-b951-d872f2087c98
parent b4c77597
// 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/ui/app_list/drive/drive_app_converter.h"
#include <algorithm>
#include <set>
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/install_tracker.h"
#include "chrome/browser/image_decoder.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/constants.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_status.h"
#include "third_party/skia/include/core/SkBitmap.h"
using content::BrowserThread;
// IconFetcher downloads |icon_url| using |converter|'s profile. The icon
// url is passed from a DriveAppInfo and should follow icon url definition
// in Drive API:
// https://developers.google.com/drive/v2/reference/apps#resource
// Each icon url represents a single image associated with a certain size.
class DriveAppConverter::IconFetcher : public net::URLFetcherDelegate,
public ImageDecoder::Delegate {
public:
IconFetcher(DriveAppConverter* converter,
const GURL& icon_url,
int expected_size)
: converter_(converter),
icon_url_(icon_url),
expected_size_(expected_size) {}
virtual ~IconFetcher() {
if (image_decoder_.get())
image_decoder_->set_delegate(NULL);
}
void Start() {
fetcher_.reset(
net::URLFetcher::Create(icon_url_, net::URLFetcher::GET, this));
fetcher_->SetRequestContext(converter_->profile_->GetRequestContext());
fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
fetcher_->Start();
}
const GURL& icon_url() const { return icon_url_; }
const SkBitmap& icon() const { return icon_; }
private:
// net::URLFetcherDelegate overrides:
virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
CHECK_EQ(fetcher_.get(), source);
scoped_ptr<net::URLFetcher> fetcher(fetcher_.Pass());
if (!fetcher->GetStatus().is_success() ||
fetcher->GetResponseCode() != net::HTTP_OK) {
converter_->OnIconFetchComplete(this);
return;
}
std::string unsafe_icon_data;
fetcher->GetResponseAsString(&unsafe_icon_data);
image_decoder_ =
new ImageDecoder(this, unsafe_icon_data, ImageDecoder::DEFAULT_CODEC);
image_decoder_->Start(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI));
}
// ImageDecoder::Delegate overrides:
virtual void OnImageDecoded(const ImageDecoder* decoder,
const SkBitmap& decoded_image) OVERRIDE {
if (decoded_image.width() == expected_size_)
icon_ = decoded_image;
converter_->OnIconFetchComplete(this);
}
virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE {
converter_->OnIconFetchComplete(this);
}
DriveAppConverter* converter_;
const GURL icon_url_;
const int expected_size_;
scoped_ptr<net::URLFetcher> fetcher_;
scoped_refptr<ImageDecoder> image_decoder_;
SkBitmap icon_;
DISALLOW_COPY_AND_ASSIGN(IconFetcher);
};
DriveAppConverter::DriveAppConverter(Profile* profile,
const drive::DriveAppInfo& app_info,
const FinishedCallback& finished_callback)
: profile_(profile),
app_info_(app_info),
app_(NULL),
finished_callback_(finished_callback) {
DCHECK(profile_);
}
DriveAppConverter::~DriveAppConverter() {
PostInstallCleanUp();
}
void DriveAppConverter::Start() {
if (app_info_.app_name.empty() ||
!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;
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;
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;
IconFetcher* fetcher = new IconFetcher(this, icon_url, icon_size);
fetchers_.push_back(fetcher); // Pass ownership to |fetchers|.
fetcher->Start();
}
if (fetchers_.empty())
StartInstall();
}
void DriveAppConverter::OnIconFetchComplete(const IconFetcher* fetcher) {
const SkBitmap& icon = fetcher->icon();
if (!icon.empty() && icon.width() != 0) {
WebApplicationInfo::IconInfo icon_info;
icon_info.url = fetcher->icon_url();
icon_info.data = icon;
icon_info.width = icon.width();
icon_info.height = icon.height();
web_app_.icons.push_back(icon_info);
}
fetchers_.erase(std::find(fetchers_.begin(), fetchers_.end(), fetcher));
if (fetchers_.empty())
StartInstall();
}
void DriveAppConverter::StartInstall() {
DCHECK(!crx_installer_);
crx_installer_ = extensions::CrxInstaller::CreateSilent(
extensions::ExtensionSystem::Get(profile_)->extension_service());
extensions::InstallTracker::Get(profile_)->AddObserver(this);
crx_installer_->InstallWebApp(web_app_);
}
void DriveAppConverter::PostInstallCleanUp() {
if (!crx_installer_)
return;
extensions::InstallTracker::Get(profile_)->RemoveObserver(this);
crx_installer_ = NULL;
}
void DriveAppConverter::OnFinishCrxInstall(const std::string& extension_id,
bool success) {
if (!crx_installer_->extension() ||
crx_installer_->extension()->id() != extension_id) {
return;
}
app_ = crx_installer_->extension();
finished_callback_.Run(this, success);
PostInstallCleanUp();
}
// 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_UI_APP_LIST_DRIVE_DRIVE_APP_CONVERTER_H_
#define CHROME_BROWSER_UI_APP_LIST_DRIVE_DRIVE_APP_CONVERTER_H_
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_vector.h"
#include "chrome/browser/drive/drive_app_registry.h"
#include "chrome/browser/extensions/install_observer.h"
#include "chrome/common/web_application_info.h"
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 {
public:
typedef base::Callback<void(const DriveAppConverter*, bool success)>
FinishedCallback;
DriveAppConverter(Profile* profile,
const drive::DriveAppInfo& app_info,
const FinishedCallback& finished_callback);
virtual ~DriveAppConverter();
void Start();
const drive::DriveAppInfo& app_info() const { return app_info_; }
const extensions::Extension* app() const { return app_; }
private:
class IconFetcher;
// Callbacks from IconFetcher.
void OnIconFetchComplete(const IconFetcher* fetcher);
void StartInstall();
void PostInstallCleanUp();
// extensions::InstallObserver:
virtual void OnFinishCrxInstall(const std::string& extension_id,
bool success) OVERRIDE;
Profile* profile_;
const drive::DriveAppInfo app_info_;
WebApplicationInfo web_app_;
const extensions::Extension* app_;
ScopedVector<IconFetcher> fetchers_;
scoped_refptr<extensions::CrxInstaller> crx_installer_;
FinishedCallback finished_callback_;
DISALLOW_COPY_AND_ASSIGN(DriveAppConverter);
};
#endif // CHROME_BROWSER_UI_APP_LIST_DRIVE_DRIVE_APP_CONVERTER_H_
// 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/ui/app_list/drive/drive_app_converter.h"
#include <utility>
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/version.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/common/permissions/permission_set.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kAppName[] = "Test drive app";
const char kAppUrl[] = "http://foobar.com/drive_app";
} // namespace
class DriveAppConverterTest : public ExtensionBrowserTest {
public:
DriveAppConverterTest() {}
virtual ~DriveAppConverterTest() {}
// ExtensionBrowserTest:
virtual void SetUpOnMainThread() OVERRIDE {
ExtensionBrowserTest::SetUpOnMainThread();
base::FilePath test_data_dir;
PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
embedded_test_server()->ServeFilesFromDirectory(test_data_dir);
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
}
void InstallAndWaitFinish(const drive::DriveAppInfo& drive_app) {
runner_ = new content::MessageLoopRunner;
converter_.reset(new DriveAppConverter(
profile(),
drive_app,
base::Bind(&DriveAppConverterTest::ConverterFinished,
base::Unretained(this))));
converter_->Start();
runner_->Run();
}
GURL GetTestUrl(const std::string& path) {
return embedded_test_server()->base_url().Resolve(path);
}
drive::DriveAppInfo GetTestDriveApp() {
// Define four icons. icon1.png is 16x16 and good to use. icon2.png is
// 16x16 but claims to be 32x32 and should be dropped. icon3.png is 66x66
// and not a valid extension icon size and should be dropped too. The forth
// one is icon2.png with 16x16 but should be ignored because 16x16 already
// has icon1.png as its resource.
drive::DriveAppInfo::IconList app_icons;
app_icons.push_back(std::make_pair(16, GetTestUrl("extensions/icon1.png")));
app_icons.push_back(std::make_pair(32, GetTestUrl("extensions/icon2.png")));
app_icons.push_back(std::make_pair(66, GetTestUrl("extensions/icon3.png")));
app_icons.push_back(std::make_pair(16, GetTestUrl("extensions/icon2.png")));
drive::DriveAppInfo::IconList document_icons;
return drive::DriveAppInfo("fake_drive_app_id",
"fake_product_id",
app_icons,
document_icons,
kAppName,
GURL(kAppUrl),
true);
}
const DriveAppConverter* converter() const { return converter_.get(); }
private:
void ConverterFinished(const DriveAppConverter* converter, bool success) {
if (runner_)
runner_->Quit();
}
scoped_ptr<DriveAppConverter> converter_;
scoped_refptr<content::MessageLoopRunner> runner_;
DISALLOW_COPY_AND_ASSIGN(DriveAppConverterTest);
};
IN_PROC_BROWSER_TEST_F(DriveAppConverterTest, GoodApp) {
InstallAndWaitFinish(GetTestDriveApp());
const extensions::Extension* app = converter()->app();
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(extensions::LAUNCH_CONTAINER_TAB,
extensions::AppLaunchInfo::GetLaunchContainer(app));
EXPECT_EQ(0u, app->GetActivePermissions()->apis().size());
EXPECT_EQ(1u, extensions::IconsInfo::GetIcons(app).map().size());
const extensions::Extension* installed =
extensions::ExtensionSystem::Get(profile())
->extension_service()
->GetInstalledExtension(app->id());
EXPECT_EQ(app, installed);
}
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);
drive::DriveAppInfo no_url = GetTestDriveApp();
no_url.create_url = GURL();
InstallAndWaitFinish(no_url);
EXPECT_TRUE(converter()->app() == NULL);
}
IN_PROC_BROWSER_TEST_F(DriveAppConverterTest, InstallTwice) {
InstallAndWaitFinish(GetTestDriveApp());
const extensions::Extension* first_install = converter()->app();
ASSERT_TRUE(first_install != NULL);
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();
ASSERT_TRUE(second_install != NULL);
// Two different app instances.
ASSERT_NE(first_install, second_install);
EXPECT_EQ(first_install_id, second_install->id());
EXPECT_GE(second_install->version()->CompareTo(first_install_version), 0);
}
...@@ -170,6 +170,8 @@ ...@@ -170,6 +170,8 @@
'browser/ui/app_list/app_list_view_delegate.h', 'browser/ui/app_list/app_list_view_delegate.h',
'browser/ui/app_list/chrome_signin_delegate.cc', 'browser/ui/app_list/chrome_signin_delegate.cc',
'browser/ui/app_list/chrome_signin_delegate.h', 'browser/ui/app_list/chrome_signin_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.cc',
'browser/ui/app_list/extension_app_item.h', 'browser/ui/app_list/extension_app_item.h',
'browser/ui/app_list/extension_app_model_builder.cc', 'browser/ui/app_list/extension_app_model_builder.cc',
......
...@@ -1325,6 +1325,7 @@ ...@@ -1325,6 +1325,7 @@
'browser/translate/translate_manager_browsertest.cc', 'browser/translate/translate_manager_browsertest.cc',
'browser/ui/app_list/app_list_controller_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/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/people/people_provider_browsertest.cc',
'browser/ui/app_list/search/webstore/webstore_provider_browsertest.cc', 'browser/ui/app_list/search/webstore/webstore_provider_browsertest.cc',
'browser/ui/ash/accelerator_commands_browsertest.cc', 'browser/ui/ash/accelerator_commands_browsertest.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment