blob: b78068266209d59728d8f57ea081c42782bf46c9 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/extensions/wallpaper_api.h"
#include <string>
#include <utility>
#include <vector>
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/stringprintf.h"
#include "base/threading/worker_pool.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/extensions/wallpaper_private_api.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "components/wallpaper/wallpaper_layout.h"
#include "extensions/browser/event_router.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 "url/gurl.h"
using base::BinaryValue;
using content::BrowserThread;
typedef base::Callback<void(bool success, const std::string&)> FetchCallback;
namespace set_wallpaper = extensions::api::wallpaper::SetWallpaper;
namespace {
class WallpaperFetcher : public net::URLFetcherDelegate {
public:
WallpaperFetcher() {}
~WallpaperFetcher() override {}
void FetchWallpaper(const GURL& url, FetchCallback callback) {
CancelPreviousFetch();
callback_ = callback;
url_fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this);
url_fetcher_->SetRequestContext(
g_browser_process->system_request_context());
url_fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
url_fetcher_->Start();
}
private:
// URLFetcherDelegate overrides:
void OnURLFetchComplete(const net::URLFetcher* source) override {
DCHECK(url_fetcher_.get() == source);
bool success = source->GetStatus().is_success() &&
source->GetResponseCode() == net::HTTP_OK;
std::string response;
if (success) {
source->GetResponseAsString(&response);
} else {
response = base::StringPrintf(
"Downloading wallpaper %s failed. The response code is %d.",
source->GetOriginalURL().ExtractFileName().c_str(),
source->GetResponseCode());
}
url_fetcher_.reset();
callback_.Run(success, response);
callback_.Reset();
}
void CancelPreviousFetch() {
if (url_fetcher_.get()) {
callback_.Run(false, wallpaper_api_util::kCancelWallpaperMessage);
callback_.Reset();
url_fetcher_.reset();
}
}
std::unique_ptr<net::URLFetcher> url_fetcher_;
FetchCallback callback_;
};
base::LazyInstance<WallpaperFetcher> g_wallpaper_fetcher =
LAZY_INSTANCE_INITIALIZER;
// Gets the |User| for a given |BrowserContext|. The function will only return
// valid objects.
const user_manager::User* GetUserFromBrowserContext(
content::BrowserContext* context) {
Profile* profile = Profile::FromBrowserContext(context);
DCHECK(profile);
const user_manager::User* user =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
DCHECK(user);
return user;
}
} // namespace
WallpaperSetWallpaperFunction::WallpaperSetWallpaperFunction() {
}
WallpaperSetWallpaperFunction::~WallpaperSetWallpaperFunction() {
}
bool WallpaperSetWallpaperFunction::RunAsync() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
params_ = set_wallpaper::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params_);
// Gets account id from the caller, ensuring multiprofile compatibility.
const user_manager::User* user = GetUserFromBrowserContext(browser_context());
account_id_ = user->GetAccountId();
chromeos::WallpaperManager* wallpaper_manager =
chromeos::WallpaperManager::Get();
wallpaper_files_id_ = wallpaper_manager->GetFilesId(account_id_);
if (params_->details.data) {
StartDecode(*params_->details.data);
} else if (params_->details.url) {
GURL wallpaper_url(*params_->details.url);
if (wallpaper_url.is_valid()) {
g_wallpaper_fetcher.Get().FetchWallpaper(
wallpaper_url,
base::Bind(&WallpaperSetWallpaperFunction::OnWallpaperFetched, this));
} else {
SetError("URL is invalid.");
SendResponse(false);
}
} else {
SetError("Either url or data field is required.");
SendResponse(false);
}
return true;
}
void WallpaperSetWallpaperFunction::OnWallpaperDecoded(
const gfx::ImageSkia& image) {
chromeos::WallpaperManager* wallpaper_manager =
chromeos::WallpaperManager::Get();
base::FilePath thumbnail_path = wallpaper_manager->GetCustomWallpaperPath(
wallpaper::kThumbnailWallpaperSubDir, wallpaper_files_id_,
params_->details.filename);
scoped_refptr<base::SequencedTaskRunner> task_runner =
BrowserThread::GetBlockingPool()
->GetSequencedTaskRunnerWithShutdownBehavior(
BrowserThread::GetBlockingPool()->GetNamedSequenceToken(
wallpaper::kWallpaperSequenceTokenName),
base::SequencedWorkerPool::BLOCK_SHUTDOWN);
wallpaper::WallpaperLayout layout = wallpaper_api_util::GetLayoutEnum(
extensions::api::wallpaper::ToString(params_->details.layout));
wallpaper_api_util::RecordCustomWallpaperLayout(layout);
bool update_wallpaper =
account_id_ ==
user_manager::UserManager::Get()->GetActiveUser()->GetAccountId();
wallpaper_manager->SetCustomWallpaper(
account_id_, wallpaper_files_id_, params_->details.filename, layout,
user_manager::User::CUSTOMIZED, image, update_wallpaper);
unsafe_wallpaper_decoder_ = NULL;
// Save current extension name. It will be displayed in the component
// wallpaper picker app. If current extension is the component wallpaper
// picker, set an empty string.
// TODO(xdai): This preference is unused now. For compatiblity concern, we
// need to keep it until it's safe to clean it up.
Profile* profile = Profile::FromBrowserContext(browser_context());
if (extension()->id() == extension_misc::kWallpaperManagerId) {
profile->GetPrefs()->SetString(prefs::kCurrentWallpaperAppName,
std::string());
} else {
profile->GetPrefs()->SetString(prefs::kCurrentWallpaperAppName,
extension()->name());
}
if (!params_->details.thumbnail)
SendResponse(true);
// We need to generate thumbnail image anyway to make the current third party
// wallpaper syncable through different devices.
image.EnsureRepsForSupportedScales();
std::unique_ptr<gfx::ImageSkia> deep_copy(image.DeepCopy());
// Generates thumbnail before call api function callback. We can then
// request thumbnail in the javascript callback.
task_runner->PostTask(
FROM_HERE,
base::Bind(&WallpaperSetWallpaperFunction::GenerateThumbnail, this,
thumbnail_path, base::Passed(std::move(deep_copy))));
}
void WallpaperSetWallpaperFunction::GenerateThumbnail(
const base::FilePath& thumbnail_path,
std::unique_ptr<gfx::ImageSkia> image) {
wallpaper::AssertCalledOnWallpaperSequence();
if (!base::PathExists(thumbnail_path.DirName()))
base::CreateDirectory(thumbnail_path.DirName());
scoped_refptr<base::RefCountedBytes> original_data;
scoped_refptr<base::RefCountedBytes> thumbnail_data;
chromeos::WallpaperManager::Get()->ResizeImage(
*image, wallpaper::WALLPAPER_LAYOUT_STRETCH, image->width(),
image->height(), &original_data, NULL);
chromeos::WallpaperManager::Get()->ResizeImage(
*image, wallpaper::WALLPAPER_LAYOUT_STRETCH,
wallpaper::kWallpaperThumbnailWidth, wallpaper::kWallpaperThumbnailHeight,
&thumbnail_data, NULL);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&WallpaperSetWallpaperFunction::ThumbnailGenerated, this,
base::RetainedRef(original_data),
base::RetainedRef(thumbnail_data)));
}
void WallpaperSetWallpaperFunction::ThumbnailGenerated(
base::RefCountedBytes* original_data,
base::RefCountedBytes* thumbnail_data) {
std::unique_ptr<BinaryValue> original_result =
BinaryValue::CreateWithCopiedBuffer(
reinterpret_cast<const char*>(original_data->front()),
original_data->size());
std::unique_ptr<BinaryValue> thumbnail_result =
BinaryValue::CreateWithCopiedBuffer(
reinterpret_cast<const char*>(thumbnail_data->front()),
thumbnail_data->size());
if (params_->details.thumbnail) {
SetResult(thumbnail_result->CreateDeepCopy());
SendResponse(true);
}
// Inform the native Wallpaper Picker Application that the current wallpaper
// has been modified by a third party application.
if (extension()->id() != extension_misc::kWallpaperManagerId) {
Profile* profile = Profile::FromBrowserContext(browser_context());
extensions::EventRouter* event_router =
extensions::EventRouter::Get(profile);
std::unique_ptr<base::ListValue> event_args(new base::ListValue());
event_args->Append(original_result->CreateDeepCopy());
event_args->Append(thumbnail_result->CreateDeepCopy());
event_args->AppendString(
extensions::api::wallpaper::ToString(params_->details.layout));
// Setting wallpaper from right click menu in 'Files' app is a feature that
// was implemented in crbug.com/578935. Since 'Files' app is a built-in v1
// app in ChromeOS, we should treat it slightly differently with other third
// party apps: the wallpaper set by the 'Files' app should still be syncable
// and it should not appear in the wallpaper grid in the Wallpaper Picker.
// But we should not display the 'wallpaper-set-by-mesage' since it might
// introduce confusion as shown in crbug.com/599407.
event_args->AppendString(
(extension()->id() == file_manager::kFileManagerAppId)
? std::string()
: extension()->name());
std::unique_ptr<extensions::Event> event(new extensions::Event(
extensions::events::WALLPAPER_PRIVATE_ON_WALLPAPER_CHANGED_BY_3RD_PARTY,
extensions::api::wallpaper_private::OnWallpaperChangedBy3rdParty::
kEventName,
std::move(event_args)));
event_router->DispatchEventToExtension(extension_misc::kWallpaperManagerId,
std::move(event));
}
}
void WallpaperSetWallpaperFunction::OnWallpaperFetched(
bool success,
const std::string& response) {
if (success) {
params_->details.data.reset(
new std::vector<char>(response.begin(), response.end()));
StartDecode(*params_->details.data);
} else {
SetError(response);
SendResponse(false);
}
}