1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/extensions/wallpaper_api.h"
6
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "ash/public/cpp/wallpaper_types.h"
12 #include "base/bind.h"
13 #include "base/check_op.h"
14 #include "base/files/file_util.h"
15 #include "base/lazy_instance.h"
16 #include "base/sequenced_task_runner.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/values.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/chromeos/extensions/wallpaper_private_api.h"
21 #include "chrome/browser/chromeos/file_manager/app_id.h"
22 #include "chrome/browser/chromeos/profiles/profile_helper.h"
23 #include "chrome/browser/net/system_network_context_manager.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
26 #include "chrome/common/chrome_paths.h"
27 #include "chrome/common/extensions/extension_constants.h"
28 #include "components/user_manager/user.h"
29 #include "components/user_manager/user_manager.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "extensions/browser/event_router.h"
32 #include "net/base/load_flags.h"
33 #include "net/http/http_response_headers.h"
34 #include "services/network/public/cpp/resource_request.h"
35 #include "services/network/public/cpp/simple_url_loader.h"
36 #include "services/network/public/mojom/url_loader_factory.mojom.h"
37 #include "url/gurl.h"
38
39 using base::Value;
40 using content::BrowserThread;
41
42 typedef base::Callback<void(bool success, const std::string&)> FetchCallback;
43
44 namespace set_wallpaper = extensions::api::wallpaper::SetWallpaper;
45
46 namespace {
47
48 class WallpaperFetcher {
49 public:
WallpaperFetcher()50 WallpaperFetcher() {}
51
FetchWallpaper(const GURL & url,FetchCallback callback)52 void FetchWallpaper(const GURL& url, FetchCallback callback) {
53 CancelPreviousFetch();
54 original_url_ = url;
55 callback_ = callback;
56
57 net::NetworkTrafficAnnotationTag traffic_annotation =
58 net::DefineNetworkTrafficAnnotation("wallpaper_fetcher", R"(
59 semantics {
60 sender: "Wallpaper Fetcher"
61 description:
62 "Chrome OS downloads wallpaper upon user request."
63 trigger:
64 "When an app or extension requests to download "
65 "a wallpaper from a remote URL."
66 data:
67 "User-selected image."
68 destination: WEBSITE
69 }
70 policy {
71 cookies_allowed: YES
72 cookies_store: "user"
73 setting:
74 "This feature cannot be disabled by settings, but it is only "
75 "triggered by user request."
76 policy_exception_justification: "Not implemented."
77 })");
78 auto resource_request = std::make_unique<network::ResourceRequest>();
79 resource_request->url = original_url_;
80 resource_request->load_flags = net::LOAD_DISABLE_CACHE;
81 simple_loader_ = network::SimpleURLLoader::Create(
82 std::move(resource_request), traffic_annotation);
83 network::mojom::URLLoaderFactory* loader_factory =
84 g_browser_process->system_network_context_manager()
85 ->GetURLLoaderFactory();
86 simple_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
87 loader_factory,
88 base::BindOnce(&WallpaperFetcher::OnSimpleLoaderComplete,
89 base::Unretained(this)));
90 }
91
92 private:
OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body)93 void OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body) {
94 std::string response;
95 bool success = false;
96 if (response_body) {
97 response = std::move(*response_body);
98 success = true;
99 } else if (simple_loader_->ResponseInfo() &&
100 simple_loader_->ResponseInfo()->headers) {
101 int response_code =
102 simple_loader_->ResponseInfo()->headers->response_code();
103 response = base::StringPrintf(
104 "Downloading wallpaper %s failed. The response code is %d.",
105 original_url_.ExtractFileName().c_str(), response_code);
106 }
107
108 simple_loader_.reset();
109 callback_.Run(success, response);
110 callback_.Reset();
111 }
112
CancelPreviousFetch()113 void CancelPreviousFetch() {
114 if (simple_loader_.get()) {
115 callback_.Run(false, wallpaper_api_util::kCancelWallpaperMessage);
116 callback_.Reset();
117 simple_loader_.reset();
118 }
119 }
120
121 GURL original_url_;
122 std::unique_ptr<network::SimpleURLLoader> simple_loader_;
123 FetchCallback callback_;
124 };
125
126 base::LazyInstance<WallpaperFetcher>::DestructorAtExit g_wallpaper_fetcher =
127 LAZY_INSTANCE_INITIALIZER;
128
129 // Gets the |User| for a given |BrowserContext|. The function will only return
130 // valid objects.
GetUserFromBrowserContext(content::BrowserContext * context)131 const user_manager::User* GetUserFromBrowserContext(
132 content::BrowserContext* context) {
133 Profile* profile = Profile::FromBrowserContext(context);
134 DCHECK(profile);
135 const user_manager::User* user =
136 chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
137 DCHECK(user);
138 return user;
139 }
140
141 } // namespace
142
WallpaperSetWallpaperFunction()143 WallpaperSetWallpaperFunction::WallpaperSetWallpaperFunction() {
144 }
145
~WallpaperSetWallpaperFunction()146 WallpaperSetWallpaperFunction::~WallpaperSetWallpaperFunction() {
147 }
148
Run()149 ExtensionFunction::ResponseAction WallpaperSetWallpaperFunction::Run() {
150 DCHECK_CURRENTLY_ON(BrowserThread::UI);
151 params_ = set_wallpaper::Params::Create(*args_);
152 EXTENSION_FUNCTION_VALIDATE(params_);
153
154 // Gets account id from the caller, ensuring multiprofile compatibility.
155 const user_manager::User* user = GetUserFromBrowserContext(browser_context());
156 account_id_ = user->GetAccountId();
157 wallpaper_files_id_ =
158 WallpaperControllerClient::Get()->GetFilesId(account_id_);
159
160 if (params_->details.data) {
161 StartDecode(*params_->details.data);
162 // StartDecode() responds asynchronously.
163 return RespondLater();
164 }
165
166 if (!params_->details.url)
167 return RespondNow(Error("Either url or data field is required."));
168
169 GURL wallpaper_url(*params_->details.url);
170 if (!wallpaper_url.is_valid())
171 return RespondNow(Error("URL is invalid."));
172
173 g_wallpaper_fetcher.Get().FetchWallpaper(
174 wallpaper_url,
175 base::Bind(&WallpaperSetWallpaperFunction::OnWallpaperFetched, this));
176 // FetchWallpaper() repsonds asynchronously.
177 return RespondLater();
178 }
179
OnWallpaperDecoded(const gfx::ImageSkia & image)180 void WallpaperSetWallpaperFunction::OnWallpaperDecoded(
181 const gfx::ImageSkia& image) {
182 ash::WallpaperLayout layout = wallpaper_api_util::GetLayoutEnum(
183 extensions::api::wallpaper::ToString(params_->details.layout));
184 wallpaper_api_util::RecordCustomWallpaperLayout(layout);
185
186 const std::string file_name =
187 base::FilePath(params_->details.filename).BaseName().value();
188 WallpaperControllerClient::Get()->SetCustomWallpaper(
189 account_id_, wallpaper_files_id_, file_name, layout, image,
190 /*preview_mode=*/false);
191 unsafe_wallpaper_decoder_ = nullptr;
192
193 // We need to generate thumbnail image anyway to make the current third party
194 // wallpaper syncable through different devices.
195 image.EnsureRepsForSupportedScales();
196 std::vector<uint8_t> thumbnail_data = GenerateThumbnail(
197 image, gfx::Size(kWallpaperThumbnailWidth, kWallpaperThumbnailHeight));
198
199 // Inform the native Wallpaper Picker Application that the current wallpaper
200 // has been modified by a third party application.
201 if (extension()->id() != extension_misc::kWallpaperManagerId) {
202 Profile* profile = Profile::FromBrowserContext(browser_context());
203 extensions::EventRouter* event_router =
204 extensions::EventRouter::Get(profile);
205
206 base::Value event_args(Value::Type::LIST);
207 event_args.Append(Value(GenerateThumbnail(image, image.size())));
208 event_args.Append(Value(thumbnail_data));
209 event_args.Append(
210 extensions::api::wallpaper::ToString(params_->details.layout));
211 // Setting wallpaper from right click menu in 'Files' app is a feature that
212 // was implemented in crbug.com/578935. Since 'Files' app is a built-in v1
213 // app in ChromeOS, we should treat it slightly differently with other third
214 // party apps: the wallpaper set by the 'Files' app should still be syncable
215 // and it should not appear in the wallpaper grid in the Wallpaper Picker.
216 // But we should not display the 'wallpaper-set-by-mesage' since it might
217 // introduce confusion as shown in crbug.com/599407.
218 event_args.Append((extension()->id() == file_manager::kFileManagerAppId)
219 ? base::StringPiece()
220 : extension()->name());
221 std::unique_ptr<extensions::Event> event(new extensions::Event(
222 extensions::events::WALLPAPER_PRIVATE_ON_WALLPAPER_CHANGED_BY_3RD_PARTY,
223 extensions::api::wallpaper_private::OnWallpaperChangedBy3rdParty::
224 kEventName,
225 base::ListValue::From(std::make_unique<Value>(std::move(event_args)))));
226 event_router->DispatchEventToExtension(extension_misc::kWallpaperManagerId,
227 std::move(event));
228 }
229
230 Respond(params_->details.thumbnail
231 ? OneArgument(Value(std::move(thumbnail_data)))
232 : NoArguments());
233 }
234
OnWallpaperFetched(bool success,const std::string & response)235 void WallpaperSetWallpaperFunction::OnWallpaperFetched(
236 bool success,
237 const std::string& response) {
238 if (success) {
239 params_->details.data.reset(
240 new std::vector<uint8_t>(response.begin(), response.end()));
241 StartDecode(*params_->details.data);
242 // StartDecode() will Respond later through OnWallpaperDecoded()
243 } else {
244 Respond(Error(response));
245 }
246 }
247