ambient_photo_cache.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. // Copyright 2020 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. #include "ash/ambient/ambient_photo_cache.h"
  5. #include <fstream>
  6. #include <iostream>
  7. #include "ash/ambient/ambient_access_token_controller.h"
  8. #include "ash/ambient/ambient_constants.h"
  9. #include "ash/constants/ash_features.h"
  10. #include "ash/public/cpp/ambient/ambient_client.h"
  11. #include "ash/public/cpp/ambient/proto/photo_cache_entry.pb.h"
  12. #include "base/bind.h"
  13. #include "base/files/file_util.h"
  14. #include "base/memory/weak_ptr.h"
  15. #include "base/path_service.h"
  16. #include "base/strings/string_number_conversions.h"
  17. #include "base/system/sys_info.h"
  18. #include "base/task/sequenced_task_runner.h"
  19. #include "base/task/task_traits.h"
  20. #include "base/task/thread_pool.h"
  21. #include "services/data_decoder/public/cpp/decode_image.h"
  22. #include "services/network/public/cpp/resource_request.h"
  23. #include "services/network/public/cpp/shared_url_loader_factory.h"
  24. #include "services/network/public/cpp/simple_url_loader.h"
  25. #include "services/network/public/mojom/url_response_head.mojom.h"
  26. #include "ui/gfx/image/image_skia.h"
  27. namespace ash {
  28. namespace {
  29. constexpr net::NetworkTrafficAnnotationTag kAmbientPhotoCacheNetworkTag =
  30. net::DefineNetworkTrafficAnnotation("ambient_photo_cache", R"(
  31. semantics {
  32. sender: "Ambient photo"
  33. description:
  34. "Get ambient photo from url to store limited number of photos in "
  35. "the device cache. This is used to show the screensaver when the "
  36. "user is idle. The url can be Backdrop service to provide pictures"
  37. " from internal gallery, weather/time photos served by Google, or "
  38. "user selected album from Google photos."
  39. trigger:
  40. "Triggered by a photo refresh timer, after the device has been "
  41. "idle and the battery is charging."
  42. data: "None."
  43. destination: GOOGLE_OWNED_SERVICE
  44. }
  45. policy {
  46. cookies_allowed: NO
  47. setting:
  48. "This feature is off by default and can be overridden by users."
  49. policy_exception_justification:
  50. "This feature is set by user settings.ambient_mode.enabled pref. "
  51. "The user setting is per device and cannot be overriden by admin."
  52. })");
  53. void ToImageSkia(base::OnceCallback<void(const gfx::ImageSkia&)> callback,
  54. const SkBitmap& image) {
  55. if (image.isNull()) {
  56. std::move(callback).Run(gfx::ImageSkia());
  57. return;
  58. }
  59. gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(image);
  60. image_skia.MakeThreadSafe();
  61. std::move(callback).Run(image_skia);
  62. }
  63. // Helper function to extract response code from |SimpleURLLoader|.
  64. int GetResponseCode(network::SimpleURLLoader* simple_loader) {
  65. if (simple_loader->ResponseInfo() && simple_loader->ResponseInfo()->headers)
  66. return simple_loader->ResponseInfo()->headers->response_code();
  67. else
  68. return -1;
  69. }
  70. std::unique_ptr<network::SimpleURLLoader> CreateSimpleURLLoader(
  71. const std::string& url,
  72. const std::string& token) {
  73. auto resource_request = std::make_unique<network::ResourceRequest>();
  74. resource_request->url = GURL(url);
  75. resource_request->method = "GET";
  76. resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
  77. if (token.empty())
  78. DVLOG(2) << "Failed to fetch access token";
  79. else
  80. resource_request->headers.SetHeader("Authorization", "Bearer " + token);
  81. return network::SimpleURLLoader::Create(std::move(resource_request),
  82. kAmbientPhotoCacheNetworkTag);
  83. }
  84. bool CreateDirIfNotExists(const base::FilePath& path) {
  85. return base::DirectoryExists(path) || base::CreateDirectory(path);
  86. }
  87. bool WriteOrDeleteFile(const base::FilePath& path,
  88. const ambient::PhotoCacheEntry& cache_entry) {
  89. // If the primary photo is empty, the same as the related photo.
  90. if (!cache_entry.has_primary_photo() ||
  91. cache_entry.primary_photo().image().empty()) {
  92. base::DeleteFile(path);
  93. return false;
  94. }
  95. if (!CreateDirIfNotExists(path.DirName())) {
  96. LOG(ERROR) << "Cannot create ambient mode directory.";
  97. return false;
  98. }
  99. if (base::SysInfo::AmountOfFreeDiskSpace(path.DirName()) <
  100. kMaxReservedAvailableDiskSpaceByte) {
  101. LOG(ERROR) << "Not enough disk space left.";
  102. return false;
  103. }
  104. // Create a temp file.
  105. base::FilePath temp_file;
  106. if (!base::CreateTemporaryFileInDir(path.DirName(), &temp_file)) {
  107. LOG(ERROR) << "Cannot create a temporary file.";
  108. return false;
  109. }
  110. // Write to the tmp file.
  111. const char* path_str = temp_file.value().c_str();
  112. std::fstream output(path_str,
  113. std::ios::out | std::ios::trunc | std::ios::binary);
  114. if (!cache_entry.SerializeToOstream(&output)) {
  115. LOG(ERROR) << "Cannot write the temporary file.";
  116. base::DeleteFile(temp_file);
  117. return false;
  118. }
  119. // Replace the current file with the temp file.
  120. if (!base::ReplaceFile(temp_file, path, /*error=*/nullptr)) {
  121. LOG(ERROR) << "Cannot replace the temporary file.";
  122. base::DeleteFile(temp_file);
  123. base::DeleteFile(path);
  124. return false;
  125. }
  126. return true;
  127. }
  128. base::FilePath GetCachePath(int cache_index, const base::FilePath& root_path) {
  129. return root_path.Append(base::NumberToString(cache_index) + kPhotoCacheExt);
  130. }
  131. // -----------------AmbientPhotoCacheImpl---------------------------------------
  132. class AmbientPhotoCacheImpl : public AmbientPhotoCache {
  133. public:
  134. AmbientPhotoCacheImpl(base::FilePath path,
  135. AmbientClient& ambient_client,
  136. AmbientAccessTokenController& access_token_controller)
  137. : root_directory_(path),
  138. task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
  139. {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
  140. base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
  141. ambient_client_(ambient_client),
  142. access_token_controller_(access_token_controller) {}
  143. ~AmbientPhotoCacheImpl() override = default;
  144. // AmbientPhotoCache:
  145. void DownloadPhoto(
  146. const std::string& url,
  147. base::OnceCallback<void(std::string&&)> callback) override {
  148. access_token_controller_.RequestAccessToken(
  149. base::BindOnce(&AmbientPhotoCacheImpl::DownloadPhotoInternal,
  150. weak_factory_.GetWeakPtr(), url, std::move(callback)));
  151. }
  152. void DownloadPhotoToFile(const std::string& url,
  153. int cache_index,
  154. base::OnceCallback<void(bool)> callback) override {
  155. auto file_path = GetCachePath(cache_index, root_directory_);
  156. base::OnceClosure download_callback;
  157. download_callback = base::BindOnce(
  158. [](base::WeakPtr<AmbientPhotoCacheImpl> weak_ptr,
  159. base::OnceCallback<void(const std::string&, const std::string&)>
  160. callback) {
  161. if (!weak_ptr)
  162. return;
  163. weak_ptr->access_token_controller_.RequestAccessToken(
  164. std::move(callback));
  165. },
  166. weak_factory_.GetWeakPtr(),
  167. base::BindOnce(&AmbientPhotoCacheImpl::DownloadPhotoToFileInternal,
  168. weak_factory_.GetWeakPtr(), url, std::move(callback),
  169. file_path));
  170. task_runner_->PostTaskAndReply(
  171. FROM_HERE,
  172. base::BindOnce(
  173. [](const base::FilePath& path) {
  174. if (!CreateDirIfNotExists(path))
  175. LOG(ERROR) << "Cannot create ambient mode directory";
  176. },
  177. root_directory_),
  178. std::move(download_callback));
  179. }
  180. void DecodePhoto(
  181. const std::string& data,
  182. base::OnceCallback<void(const gfx::ImageSkia&)> callback) override {
  183. data_decoder::DecodeImageIsolated(
  184. base::as_bytes(base::make_span(data)),
  185. data_decoder::mojom::ImageCodec::kDefault,
  186. /*shrink_to_fit=*/true, data_decoder::kDefaultMaxSizeInBytes,
  187. /*desired_image_frame_size=*/gfx::Size(),
  188. base::BindOnce(&ToImageSkia, std::move(callback)));
  189. }
  190. void WritePhotoCache(int cache_index,
  191. const ambient::PhotoCacheEntry& cache_entry,
  192. base::OnceClosure callback) override {
  193. DCHECK_LT(cache_index, kMaxNumberOfCachedImages);
  194. task_runner_->PostTaskAndReply(
  195. FROM_HERE,
  196. base::BindOnce(
  197. [](int cache_index, const base::FilePath& root_path,
  198. const ambient::PhotoCacheEntry& cache_entry) {
  199. auto cache_path = GetCachePath(cache_index, root_path);
  200. WriteOrDeleteFile(cache_path, cache_entry);
  201. },
  202. cache_index, root_directory_, cache_entry),
  203. std::move(callback));
  204. }
  205. void ReadPhotoCache(
  206. int cache_index,
  207. base::OnceCallback<void(::ambient::PhotoCacheEntry)> callback) override {
  208. task_runner_->PostTaskAndReplyWithResult(
  209. FROM_HERE,
  210. base::BindOnce(
  211. [](int cache_index, const base::FilePath& root_path) {
  212. auto cache_path = GetCachePath(cache_index, root_path);
  213. // Read the existing cache.
  214. const char* path_str = cache_path.value().c_str();
  215. std::fstream input(path_str, std::ios::in | std::ios::binary);
  216. ambient::PhotoCacheEntry cache_entry;
  217. if (!input || !cache_entry.ParseFromIstream(&input)) {
  218. LOG(ERROR) << "Unable to read photo cache";
  219. cache_entry = ::ambient::PhotoCacheEntry();
  220. base::DeleteFile(cache_path);
  221. }
  222. return cache_entry;
  223. },
  224. cache_index, root_directory_),
  225. std::move(callback));
  226. }
  227. void Clear() override {
  228. task_runner_->PostTask(FROM_HERE,
  229. base::BindOnce(
  230. [](const base::FilePath& file_path) {
  231. base::DeletePathRecursively(file_path);
  232. },
  233. root_directory_));
  234. }
  235. private:
  236. void DownloadPhotoInternal(const std::string& url,
  237. base::OnceCallback<void(std::string&&)> callback,
  238. const std::string& gaia_id,
  239. const std::string& access_token) {
  240. std::unique_ptr<network::SimpleURLLoader> simple_loader =
  241. CreateSimpleURLLoader(url, access_token);
  242. scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
  243. ambient_client_.GetURLLoaderFactory();
  244. auto* loader_ptr = simple_loader.get();
  245. auto* loader_factory_ptr = loader_factory.get();
  246. loader_ptr->DownloadToString(
  247. loader_factory_ptr,
  248. base::BindOnce(&AmbientPhotoCacheImpl::OnUrlDownloaded,
  249. weak_factory_.GetWeakPtr(), std::move(callback),
  250. std::move(simple_loader), std::move(loader_factory)),
  251. kMaxImageSizeInBytes);
  252. }
  253. void DownloadPhotoToFileInternal(const std::string& url,
  254. base::OnceCallback<void(bool)> callback,
  255. const base::FilePath& file_path,
  256. const std::string& gaia_id,
  257. const std::string& access_token) {
  258. std::unique_ptr<network::SimpleURLLoader> simple_loader =
  259. CreateSimpleURLLoader(url, access_token);
  260. scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
  261. ambient_client_.GetURLLoaderFactory();
  262. auto* loader_ptr = simple_loader.get();
  263. auto* loader_factory_ptr = loader_factory.get();
  264. // Create a temporary file path as target for download to guard against race
  265. // conditions in reading.
  266. base::FilePath temp_path =
  267. file_path.DirName().Append(base::UnguessableToken::Create().ToString());
  268. // Download to temp file first to guarantee entire image is written without
  269. // errors before attempting to read it.
  270. loader_ptr->DownloadToFile(
  271. loader_factory_ptr,
  272. base::BindOnce(&AmbientPhotoCacheImpl::OnUrlDownloadedToFile,
  273. weak_factory_.GetWeakPtr(), std::move(callback),
  274. std::move(simple_loader), std::move(loader_factory),
  275. file_path),
  276. temp_path);
  277. }
  278. void OnUrlDownloaded(
  279. base::OnceCallback<void(std::string&&)> callback,
  280. std::unique_ptr<network::SimpleURLLoader> simple_loader,
  281. scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
  282. std::unique_ptr<std::string> response_body) {
  283. if (simple_loader->NetError() == net::OK && response_body) {
  284. std::move(callback).Run(std::move(*response_body));
  285. return;
  286. }
  287. LOG(ERROR) << "Downloading to string failed with error code: "
  288. << GetResponseCode(simple_loader.get()) << " with network error "
  289. << simple_loader->NetError();
  290. std::move(callback).Run(std::string());
  291. }
  292. void OnUrlDownloadedToFile(
  293. base::OnceCallback<void(bool)> callback,
  294. std::unique_ptr<network::SimpleURLLoader> simple_loader,
  295. scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
  296. const base::FilePath& desired_path,
  297. base::FilePath temp_path) {
  298. if (simple_loader->NetError() != net::OK || temp_path.empty()) {
  299. LOG(ERROR) << "Downloading to file failed with error code: "
  300. << GetResponseCode(simple_loader.get())
  301. << " with network error " << simple_loader->NetError();
  302. if (!temp_path.empty()) {
  303. // Clean up temporary file.
  304. task_runner_->PostTask(FROM_HERE, base::BindOnce(
  305. [](const base::FilePath& path) {
  306. base::DeleteFile(path);
  307. },
  308. temp_path));
  309. }
  310. std::move(callback).Run(false);
  311. return;
  312. }
  313. // Swap the temporary file to the desired path, and then run the callback.
  314. task_runner_->PostTaskAndReplyWithResult(
  315. FROM_HERE,
  316. base::BindOnce(
  317. [](const base::FilePath& to_path, const base::FilePath& from_path) {
  318. ambient::PhotoCacheEntry cache_entry;
  319. ambient::Photo* primary_photo =
  320. cache_entry.mutable_primary_photo();
  321. std::string image;
  322. bool has_error = false;
  323. if (!base::ReadFileToString(from_path, &image)) {
  324. has_error = true;
  325. LOG(ERROR) << "Unable to read downloaded file";
  326. } else {
  327. primary_photo->set_image(std::move(image));
  328. }
  329. if (!has_error && !WriteOrDeleteFile(to_path, cache_entry)) {
  330. has_error = true;
  331. LOG(ERROR)
  332. << "Unable to move downloaded file to ambient directory";
  333. }
  334. base::DeleteFile(from_path);
  335. return !has_error;
  336. },
  337. desired_path, temp_path),
  338. std::move(callback));
  339. }
  340. const base::FilePath root_directory_;
  341. scoped_refptr<base::SequencedTaskRunner> task_runner_;
  342. AmbientClient& ambient_client_;
  343. AmbientAccessTokenController& access_token_controller_;
  344. base::WeakPtrFactory<AmbientPhotoCacheImpl> weak_factory_{this};
  345. };
  346. } // namespace
  347. // -------------- AmbientPhotoCache --------------------------------------------
  348. // static
  349. std::unique_ptr<AmbientPhotoCache> AmbientPhotoCache::Create(
  350. base::FilePath root_path,
  351. AmbientClient& ambient_client,
  352. AmbientAccessTokenController& access_token_controller) {
  353. return std::make_unique<AmbientPhotoCacheImpl>(root_path, ambient_client,
  354. access_token_controller);
  355. }
  356. } // namespace ash