Commit f9771f0b authored by Justin DeWitt's avatar Justin DeWitt Committed by Commit Bot

[EoS] Add APIs and tasks related to pulling images from the data store.

This patch:
* Adds APIs to ExploreSitesService that can request either a site image
  or a category image (which is a composite of multiple site images) as
  an SkBitmap (which is the format required for Java Bitmaps).
* Adds a task to fetch one or more images from the ExploreSitesStore,
  based on the site ID or category ID.
* The task filters based on the existence of an entry in the
  site_blacklist table (which is not populated yet but will be as a
  result of user long-press.

This DOES NOT implement conversion from byte vectors to SkBitmap (left
as a TODO.)

Bug: 867488
Change-Id: Id2e6a6abe2781b790f9ce3f597a9e747eddd4715
Reviewed-on: https://chromium-review.googlesource.com/1233176Reviewed-by: default avatarCathy Li <chili@chromium.org>
Commit-Queue: Justin DeWitt <dewittj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#592501}
parent 0d4c3201
...@@ -2151,6 +2151,8 @@ jumbo_split_static_library("browser") { ...@@ -2151,6 +2151,8 @@ jumbo_split_static_library("browser") {
"android/explore_sites/explore_sites_types.h", "android/explore_sites/explore_sites_types.h",
"android/explore_sites/get_catalog_task.cc", "android/explore_sites/get_catalog_task.cc",
"android/explore_sites/get_catalog_task.h", "android/explore_sites/get_catalog_task.h",
"android/explore_sites/get_images_task.cc",
"android/explore_sites/get_images_task.h",
"android/explore_sites/import_catalog_task.cc", "android/explore_sites/import_catalog_task.cc",
"android/explore_sites/import_catalog_task.h", "android/explore_sites/import_catalog_task.h",
"android/explore_sites/ntp_json_fetcher.cc", "android/explore_sites/ntp_json_fetcher.cc",
......
...@@ -18,6 +18,16 @@ class ExploreSitesService : public KeyedService { ...@@ -18,6 +18,16 @@ class ExploreSitesService : public KeyedService {
// Returns via callback the current catalog stored locally. // Returns via callback the current catalog stored locally.
virtual void GetCatalog(CatalogCallback callback) = 0; virtual void GetCatalog(CatalogCallback callback) = 0;
// Returns via callback the image for a category. This image is composed from
// multiple site images. The site images are checked against the user
// blacklist so that unwanted sites are not represented in the category image.
// Returns |nullptr| if there was an error, or no match.
virtual void GetCategoryImage(int category_id, BitmapCallback callback) = 0;
// Returns via callback the image for a site. This is typically the site
// favicon. Returns |nullptr| if there was an error or no match for |site_id|.
virtual void GetSiteImage(int site_id, BitmapCallback callback) = 0;
}; };
} // namespace explore_sites } // namespace explore_sites
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "chrome/browser/android/explore_sites/explore_sites_feature.h" #include "chrome/browser/android/explore_sites/explore_sites_feature.h"
#include "chrome/browser/android/explore_sites/explore_sites_store.h" #include "chrome/browser/android/explore_sites/explore_sites_store.h"
#include "chrome/browser/android/explore_sites/get_catalog_task.h" #include "chrome/browser/android/explore_sites/get_catalog_task.h"
#include "chrome/browser/android/explore_sites/get_images_task.h"
#include "chrome/browser/android/explore_sites/import_catalog_task.h" #include "chrome/browser/android/explore_sites/import_catalog_task.h"
#include "components/offline_pages/task/task.h" #include "components/offline_pages/task/task.h"
...@@ -15,6 +16,15 @@ using chrome::android::explore_sites::ExploreSitesVariation; ...@@ -15,6 +16,15 @@ using chrome::android::explore_sites::ExploreSitesVariation;
using chrome::android::explore_sites::GetExploreSitesVariation; using chrome::android::explore_sites::GetExploreSitesVariation;
namespace explore_sites { namespace explore_sites {
namespace {
const int kFaviconsPerCategoryImage = 4;
}
void DecodeImageBytes(BitmapCallback callback, EncodedImageList images) {
// TODO(freedjm): implement image decode.
NOTIMPLEMENTED();
std::move(callback).Run(nullptr);
}
ExploreSitesServiceImpl::ExploreSitesServiceImpl( ExploreSitesServiceImpl::ExploreSitesServiceImpl(
std::unique_ptr<ExploreSitesStore> store) std::unique_ptr<ExploreSitesStore> store)
...@@ -35,6 +45,22 @@ void ExploreSitesServiceImpl::GetCatalog(CatalogCallback callback) { ...@@ -35,6 +45,22 @@ void ExploreSitesServiceImpl::GetCatalog(CatalogCallback callback) {
check_for_new_catalog_ = false; check_for_new_catalog_ = false;
} }
void ExploreSitesServiceImpl::GetCategoryImage(int category_id,
BitmapCallback callback) {
task_queue_.AddTask(std::make_unique<GetImagesTask>(
explore_sites_store_.get(), category_id, kFaviconsPerCategoryImage,
base::BindOnce(&DecodeImageBytes, std::move(callback))));
// TODO(dewittj, freedjm): implement.
std::move(callback).Run(nullptr);
}
void ExploreSitesServiceImpl::GetSiteImage(int site_id,
BitmapCallback callback) {
task_queue_.AddTask(std::make_unique<GetImagesTask>(
explore_sites_store_.get(), site_id,
base::BindOnce(&DecodeImageBytes, std::move(callback))));
}
void ExploreSitesServiceImpl::Shutdown() {} void ExploreSitesServiceImpl::Shutdown() {}
void ExploreSitesServiceImpl::OnTaskQueueIsIdle() {} void ExploreSitesServiceImpl::OnTaskQueueIsIdle() {}
......
...@@ -28,6 +28,8 @@ class ExploreSitesServiceImpl : public ExploreSitesService, ...@@ -28,6 +28,8 @@ class ExploreSitesServiceImpl : public ExploreSitesService,
// ExploreSitesService implementation. // ExploreSitesService implementation.
void GetCatalog(CatalogCallback callback) override; void GetCatalog(CatalogCallback callback) override;
void GetCategoryImage(int category_id, BitmapCallback callback) override;
void GetSiteImage(int site_id, BitmapCallback callback) override;
private: private:
// KeyedService implementation: // KeyedService implementation:
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace explore_sites { namespace explore_sites {
...@@ -55,6 +56,11 @@ struct ExploreSitesCategory { ...@@ -55,6 +56,11 @@ struct ExploreSitesCategory {
using CatalogCallback = base::OnceCallback<void( using CatalogCallback = base::OnceCallback<void(
std::unique_ptr<std::vector<ExploreSitesCategory>>)>; std::unique_ptr<std::vector<ExploreSitesCategory>>)>;
using EncodedImageBytes = std::vector<uint8_t>;
using EncodedImageList = std::vector<std::unique_ptr<EncodedImageBytes>>;
using EncodedImageListCallback = base::OnceCallback<void(EncodedImageList)>;
using BitmapCallback = base::OnceCallback<void(std::unique_ptr<SkBitmap>)>;
} // namespace explore_sites } // namespace explore_sites
#endif // CHROME_BROWSER_ANDROID_EXPLORE_SITES_EXPLORE_SITES_TYPES_H_ #endif // CHROME_BROWSER_ANDROID_EXPLORE_SITES_EXPLORE_SITES_TYPES_H_
// Copyright 2018 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/android/explore_sites/get_images_task.h"
#include "chrome/browser/android/explore_sites/explore_sites_schema.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace explore_sites {
namespace {
const char kSelectCategoryImagesSql[] = R"(SELECT favicon
FROM sites
LEFT JOIN site_blacklist ON (sites.url = site_blacklist.url)
WHERE category_id = ? AND NOT removed AND site_blacklist.url IS NULL
LIMIT ?;)";
const char kSelectSiteImageSql[] =
"SELECT favicon FROM sites WHERE site_id = ?;";
} // namespace
EncodedImageList GetCategoryImagesSync(int category_id,
int max_images,
sql::Database* db) {
DCHECK(db);
sql::Statement category_statement(
db->GetCachedStatement(SQL_FROM_HERE, kSelectCategoryImagesSql));
category_statement.BindInt64(0, category_id);
category_statement.BindInt64(1, max_images);
EncodedImageList result;
while (category_statement.Step()) {
int byte_length = category_statement.ColumnByteLength(0);
result.push_back(std::make_unique<std::vector<uint8_t>>(byte_length));
category_statement.ColumnBlobAsVector(0, result.back().get());
}
if (!category_statement.Succeeded())
return EncodedImageList();
return result;
}
EncodedImageList GetSiteImageSync(int site_id, sql::Database* db) {
DCHECK(db);
sql::Statement site_statement(
db->GetCachedStatement(SQL_FROM_HERE, kSelectSiteImageSql));
site_statement.BindInt64(0, site_id);
EncodedImageList result;
while (site_statement.Step()) {
int byte_length = site_statement.ColumnByteLength(0);
result.push_back(std::make_unique<std::vector<uint8_t>>(byte_length));
site_statement.ColumnBlobAsVector(0, result.back().get());
}
if (!site_statement.Succeeded())
return EncodedImageList();
return result;
}
GetImagesTask::GetImagesTask(ExploreSitesStore* store,
int site_id,
EncodedImageListCallback callback)
: store_(store),
data_type_(DataType::kSite),
id_(site_id),
max_results_(1),
callback_(std::move(callback)),
weak_ptr_factory_(this) {}
GetImagesTask::GetImagesTask(ExploreSitesStore* store,
int category_id,
int max_images,
EncodedImageListCallback callback)
: store_(store),
data_type_(DataType::kCategory),
id_(category_id),
max_results_(max_images),
callback_(std::move(callback)),
weak_ptr_factory_(this) {}
GetImagesTask::~GetImagesTask() = default;
void GetImagesTask::Run() {
switch (data_type_) {
case DataType::kCategory:
store_->Execute(base::BindOnce(&GetCategoryImagesSync, id_, max_results_),
base::BindOnce(&GetImagesTask::FinishedExecuting,
weak_ptr_factory_.GetWeakPtr()),
EncodedImageList());
break;
case DataType::kSite:
store_->Execute(base::BindOnce(&GetSiteImageSync, id_),
base::BindOnce(&GetImagesTask::FinishedExecuting,
weak_ptr_factory_.GetWeakPtr()),
EncodedImageList());
break;
}
}
void GetImagesTask::FinishedExecuting(EncodedImageList images) {
TaskComplete();
DVLOG(1) << "Finished getting images, resulting in " << images.size()
<< " images.";
std::move(callback_).Run(std::move(images));
}
} // namespace explore_sites
// Copyright 2018 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_ANDROID_EXPLORE_SITES_GET_IMAGES_TASK_H_
#define CHROME_BROWSER_ANDROID_EXPLORE_SITES_GET_IMAGES_TASK_H_
#include <vector>
#include "chrome/browser/android/explore_sites/explore_sites_store.h"
#include "chrome/browser/android/explore_sites/explore_sites_types.h"
#include "components/offline_pages/task/task.h"
using offline_pages::Task;
namespace explore_sites {
// A task that can retrieve images from the catalog from the ExploreSitesStore.
//
// Creators can specify either a category with a maximum number of images, or
// a site (for which only 0 or 1 images will be returned).
//
// Does not do any version checking, since site_id and category_id are version-
// specific.
class GetImagesTask : public Task {
public:
GetImagesTask(ExploreSitesStore* store,
int category_id,
int max_images,
EncodedImageListCallback callback);
GetImagesTask(ExploreSitesStore* store,
int site_id,
EncodedImageListCallback callback);
~GetImagesTask() override;
private:
// Task implementation:
void Run() override;
void FinishedExecuting(EncodedImageList images);
ExploreSitesStore* store_; // outlives this class.
enum class DataType { kCategory, kSite };
DataType data_type_;
int id_;
int max_results_;
EncodedImageListCallback callback_;
base::WeakPtrFactory<GetImagesTask> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(GetImagesTask);
};
} // namespace explore_sites
#endif // CHROME_BROWSER_ANDROID_EXPLORE_SITES_GET_IMAGES_TASK_H_
// Copyright 2018 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/android/explore_sites/get_images_task.h"
#include <memory>
#include "base/logging.h"
#include "base/test/bind_test_util.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/android/explore_sites/explore_sites_schema.h"
#include "components/offline_pages/task/task.h"
#include "components/offline_pages/task/task_test_base.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using offline_pages::TaskTestBase;
namespace explore_sites {
class ExploreSitesGetImagesTaskTest : public TaskTestBase {
public:
ExploreSitesGetImagesTaskTest() = default;
~ExploreSitesGetImagesTaskTest() override = default;
void SetUp() override {
store_ = std::make_unique<ExploreSitesStore>(task_runner());
}
ExploreSitesStore* store() { return store_.get(); }
void ExecuteSync(base::RepeatingCallback<bool(sql::Database*)> query) {
store()->Execute(base::OnceCallback<bool(sql::Database*)>(query),
base::BindOnce([](bool result) { ASSERT_TRUE(result); }),
false);
RunUntilIdle();
}
void PopulateTestingCatalog();
void ExpectEmptyImageList(EncodedImageList images) {
EXPECT_EQ(0U, images.size());
}
EncodedImageListCallback StoreResult() {
return base::BindLambdaForTesting(
[&](EncodedImageList result) { last_result = std::move(result); });
}
EncodedImageList last_result;
private:
std::unique_ptr<ExploreSitesStore> store_;
DISALLOW_COPY_AND_ASSIGN(ExploreSitesGetImagesTaskTest);
};
void ExploreSitesGetImagesTaskTest::PopulateTestingCatalog() {
ExecuteSync(base::BindLambdaForTesting([](sql::Database* db) {
sql::MetaTable meta_table;
ExploreSitesSchema::InitMetaTable(db, &meta_table);
meta_table.SetValue("current_catalog", 5678);
meta_table.DeleteKey("downloading_catalog");
sql::Statement insert(db->GetUniqueStatement(R"(
INSERT INTO categories
(category_id, version, type, label)
VALUES
(3, 5678, 1, "label_1"),
(4, 5678, 2, "label_2");)"));
if (!insert.Run())
return false;
sql::Statement insert_sites(db->GetUniqueStatement(R"(
INSERT INTO sites
(site_id, url, category_id, title, favicon)
VALUES
(1, "https://www.example.com/1", 3, "example_1", "bytes1"),
(2, "https://www.example.com/2", 4, "example_2", "bytes2"),
(3, "https://www.example.com/3", 3, "example_3", "bytes3"),
(4, "https://www.example.com/4", 4, "example_4", "bytes4");
)"));
return insert_sites.Run();
}));
}
TEST_F(ExploreSitesGetImagesTaskTest, StoreFailure) {
store()->SetInitializationStatusForTest(InitializationStatus::FAILURE);
GetImagesTask task(store(), 1, StoreResult());
RunTask(&task);
EXPECT_EQ(0U, last_result.size());
}
TEST_F(ExploreSitesGetImagesTaskTest, SiteDoesNotExist) {
GetImagesTask task(store(), 43, StoreResult());
RunTask(&task);
EXPECT_EQ(0U, last_result.size());
}
TEST_F(ExploreSitesGetImagesTaskTest, CategoryDoesNotExist) {
GetImagesTask task(store(), 43 /* invalid id */, 4, StoreResult());
RunTask(&task);
EXPECT_EQ(0U, last_result.size());
}
TEST_F(ExploreSitesGetImagesTaskTest, SiteExistsAndHasFavicon) {
PopulateTestingCatalog();
GetImagesTask task(store(), 1, StoreResult());
RunTask(&task);
EXPECT_EQ(1U, last_result.size());
std::vector<uint8_t>& result = *last_result[0];
EXPECT_EQ("bytes1", std::string(result.begin(), result.end()));
last_result.clear();
GetImagesTask task2(store(), 3, StoreResult());
RunTask(&task2);
EXPECT_EQ(1U, last_result.size());
std::vector<uint8_t>& result3 = *last_result[0];
EXPECT_EQ("bytes3", std::string(result3.begin(), result3.end()));
}
TEST_F(ExploreSitesGetImagesTaskTest, SitesExistAndNotBlacklisted) {
PopulateTestingCatalog();
GetImagesTask task(store(), 3, 4, StoreResult());
RunTask(&task);
EXPECT_EQ(2U, last_result.size());
std::vector<uint8_t>& result = *last_result[0];
EXPECT_EQ("bytes1", std::string(result.begin(), result.end()));
std::vector<uint8_t>& result2 = *last_result[1];
EXPECT_EQ("bytes3", std::string(result2.begin(), result2.end()));
}
TEST_F(ExploreSitesGetImagesTaskTest, SitesExistAndBlacklisted) {
PopulateTestingCatalog();
ExecuteSync(base::BindLambdaForTesting([&](sql::Database* db) {
sql::Statement insert(db->GetUniqueStatement(R"(
INSERT INTO site_blacklist
(url, date_removed)
VALUES
("https://www.example.com/1", 123);)"));
return insert.Run();
}));
GetImagesTask task(store(), 3, 4, StoreResult());
RunTask(&task);
EXPECT_EQ(1U, last_result.size());
std::vector<uint8_t>& result = *last_result[0];
EXPECT_EQ("bytes3", std::string(result.begin(), result.end()));
last_result.clear();
}
TEST_F(ExploreSitesGetImagesTaskTest, TooManySitesToReturn) {
PopulateTestingCatalog();
// Add 3 more sites to the interesting category, but limit the site max to 4.
ExecuteSync(base::BindLambdaForTesting([&](sql::Database* db) {
sql::Statement insert(db->GetUniqueStatement(R"(
INSERT INTO sites
(site_id, url, category_id, title, favicon)
VALUES
(5, "https://www.example.com/5", 3, "example_5", "bytes5"),
(6, "https://www.example.com/6", 3, "example_6", "bytes6"),
(7, "https://www.example.com/7", 3, "example_7", "bytes7");)"));
return insert.Run();
}));
GetImagesTask task(store(), 3, 4, StoreResult());
RunTask(&task);
EXPECT_EQ(4U, last_result.size());
}
} // namespace explore_sites
...@@ -2330,6 +2330,7 @@ test("unit_tests") { ...@@ -2330,6 +2330,7 @@ test("unit_tests") {
"../browser/android/explore_sites/explore_sites_schema_unittest.cc", "../browser/android/explore_sites/explore_sites_schema_unittest.cc",
"../browser/android/explore_sites/explore_sites_store_unittest.cc", "../browser/android/explore_sites/explore_sites_store_unittest.cc",
"../browser/android/explore_sites/get_catalog_task_unittest.cc", "../browser/android/explore_sites/get_catalog_task_unittest.cc",
"../browser/android/explore_sites/get_images_task_unittest.cc",
"../browser/android/explore_sites/import_catalog_task_unittest.cc", "../browser/android/explore_sites/import_catalog_task_unittest.cc",
"../browser/android/explore_sites/ntp_json_fetcher_unittest.cc", "../browser/android/explore_sites/ntp_json_fetcher_unittest.cc",
"../browser/android/history_report/data_observer_unittest.cc", "../browser/android/history_report/data_observer_unittest.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