personalization: allow selecting local image
When a user selects a local image file, display it as the wallpaper. Call into WallpaperController->SetCustomWallpaper with the selected image. BUG=b/191086436 Cq-Include-Trybots: luci.chrome.try:linux-chromeos-chrome Change-Id: I22c66eabc0ca0483fc0966d01ec3870f87159c1a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2964369 Commit-Queue: Jeffrey Young <cowmoo@chromium.org> Reviewed-by: Xiaohui Chen <xiaohuic@chromium.org> Reviewed-by: Sam McNally <sammc@chromium.org> Cr-Commit-Position: refs/heads/master@{#894350}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
3408c982f9
commit
a040384d98
ash
public
wallpaper
chrome
browser
ash
web_applications
ui
test
data
webui
chromeos
chromeos/components/personalization_app
@ -45,6 +45,24 @@ class ASH_PUBLIC_EXPORT WallpaperController {
|
||||
const base::FilePath& custom_wallpapers,
|
||||
const base::FilePath& device_policy_wallpaper) = 0;
|
||||
|
||||
// Sets wallpaper from a local file and updates the saved wallpaper info for
|
||||
// the user.
|
||||
// |account_id|: The user's account id.
|
||||
// |wallpaper_files_id|: The file id for |account_id|.
|
||||
// |file_path|: The path of the image file to read.
|
||||
// |layout|: The layout of the wallpaper, used for wallpaper resizing.
|
||||
// |preview_mode|: If true, show the wallpaper immediately but doesn't change
|
||||
// the user wallpaper info until |ConfirmPreviewWallpaper| is
|
||||
// called.
|
||||
// |callback|: called when the image is read from file and decoded.
|
||||
using SetCustomWallpaperCallback = base::OnceCallback<void(bool success)>;
|
||||
virtual void SetCustomWallpaper(const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const base::FilePath& file_path,
|
||||
WallpaperLayout layout,
|
||||
bool preview_mode,
|
||||
SetCustomWallpaperCallback callback) = 0;
|
||||
|
||||
// Sets wallpaper from a local file and updates the saved wallpaper info for
|
||||
// the user.
|
||||
// |account_id|: The user's account id.
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "ash/public/cpp/ash_pref_names.h"
|
||||
#include "ash/public/cpp/image_downloader.h"
|
||||
#include "ash/public/cpp/shell_window_ids.h"
|
||||
#include "ash/public/cpp/wallpaper_controller.h"
|
||||
#include "ash/public/cpp/wallpaper_controller_client.h"
|
||||
#include "ash/public/cpp/wallpaper_controller_observer.h"
|
||||
#include "ash/public/cpp/wallpaper_types.h"
|
||||
@ -289,22 +290,6 @@ ColorProfileType GetColorProfileType(ColorProfile color_profile) {
|
||||
return ColorProfileType::DARK_MUTED;
|
||||
}
|
||||
|
||||
// If |read_is_successful| is true, start decoding the image, which will run
|
||||
// |callback| upon completion; if it's false, run |callback| directly with an
|
||||
// empty image.
|
||||
void OnWallpaperDataRead(LoadedCallback callback,
|
||||
std::unique_ptr<std::string> data,
|
||||
bool read_is_successful) {
|
||||
if (!read_is_successful) {
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE, base::BindOnce(std::move(callback), gfx::ImageSkia()));
|
||||
return;
|
||||
}
|
||||
// This image was once encoded to JPEG by |ResizeAndEncodeImage|.
|
||||
DecodeWallpaper(*data, data_decoder::mojom::ImageCodec::kDefault,
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
// Deletes a list of wallpaper files in |file_list|.
|
||||
void DeleteWallpaperInList(std::vector<base::FilePath> file_list) {
|
||||
for (const base::FilePath& path : file_list) {
|
||||
@ -984,6 +969,20 @@ void WallpaperControllerImpl::Init(
|
||||
SetDevicePolicyWallpaperPath(device_policy_wallpaper_path);
|
||||
}
|
||||
|
||||
void WallpaperControllerImpl::SetCustomWallpaper(
|
||||
const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const base::FilePath& file_path,
|
||||
WallpaperLayout layout,
|
||||
bool preview_mode,
|
||||
SetCustomWallpaperCallback callback) {
|
||||
ReadAndDecodeWallpaper(
|
||||
base::BindOnce(&WallpaperControllerImpl::OnCustomWallpaperDecoded,
|
||||
weak_factory_.GetWeakPtr(), account_id, wallpaper_files_id,
|
||||
file_path, layout, preview_mode, std::move(callback)),
|
||||
sequenced_task_runner_, file_path);
|
||||
}
|
||||
|
||||
void WallpaperControllerImpl::SetCustomWallpaper(
|
||||
const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
@ -1083,7 +1082,7 @@ void WallpaperControllerImpl::SetOnlineWallpaperFromData(
|
||||
}
|
||||
|
||||
const OnlineWallpaperParams params = {account_id, url, layout, preview_mode};
|
||||
LoadedCallback decoded_callback =
|
||||
DecodeImageCallback decoded_callback =
|
||||
base::BindOnce(&WallpaperControllerImpl::OnOnlineWallpaperDecoded,
|
||||
weak_factory_.GetWeakPtr(), params, /*save_file=*/true,
|
||||
std::move(callback));
|
||||
@ -1092,12 +1091,7 @@ void WallpaperControllerImpl::SetOnlineWallpaperFromData(
|
||||
.Run(CreateSolidColorWallpaper(kDefaultWallpaperColor));
|
||||
return;
|
||||
}
|
||||
// Use default codec because 1) online wallpapers may have various formats,
|
||||
// 2) the image data comes from the Chrome OS wallpaper picker and is
|
||||
// trusted (third-party wallpaper apps use |SetThirdPartyWallpaper|), 3) the
|
||||
// code path is never used on login screen (enforced by the check above).
|
||||
DecodeWallpaper(image_data, data_decoder::mojom::ImageCodec::kDefault,
|
||||
std::move(decoded_callback));
|
||||
DecodeImageData(std::move(decoded_callback), image_data);
|
||||
}
|
||||
|
||||
void WallpaperControllerImpl::SetDefaultWallpaper(
|
||||
@ -1142,7 +1136,7 @@ void WallpaperControllerImpl::SetPolicyWallpaper(
|
||||
|
||||
// Updates the screen only when the user with this account_id has logged in.
|
||||
const bool show_wallpaper = IsActiveUser(account_id);
|
||||
LoadedCallback callback = base::BindOnce(
|
||||
DecodeImageCallback callback = base::BindOnce(
|
||||
&WallpaperControllerImpl::SaveAndSetWallpaper, weak_factory_.GetWeakPtr(),
|
||||
account_id, wallpaper_files_id, kPolicyWallpaperFile, POLICY,
|
||||
WALLPAPER_LAYOUT_CENTER_CROPPED, show_wallpaper);
|
||||
@ -1151,8 +1145,7 @@ void WallpaperControllerImpl::SetPolicyWallpaper(
|
||||
std::move(callback).Run(CreateSolidColorWallpaper(kDefaultWallpaperColor));
|
||||
return;
|
||||
}
|
||||
DecodeWallpaper(data, data_decoder::mojom::ImageCodec::kDefault,
|
||||
std::move(callback));
|
||||
DecodeImageData(std::move(callback), data);
|
||||
}
|
||||
|
||||
void WallpaperControllerImpl::SetDevicePolicyWallpaperPath(
|
||||
@ -1832,7 +1825,7 @@ bool WallpaperControllerImpl::WallpaperIsAlreadyLoaded(
|
||||
}
|
||||
|
||||
void WallpaperControllerImpl::ReadAndDecodeWallpaper(
|
||||
LoadedCallback callback,
|
||||
DecodeImageCallback callback,
|
||||
scoped_refptr<base::SequencedTaskRunner> task_runner,
|
||||
const base::FilePath& file_path) {
|
||||
decode_requests_for_testing_.push_back(file_path);
|
||||
@ -1840,12 +1833,7 @@ void WallpaperControllerImpl::ReadAndDecodeWallpaper(
|
||||
std::move(callback).Run(CreateSolidColorWallpaper(kDefaultWallpaperColor));
|
||||
return;
|
||||
}
|
||||
std::string* data = new std::string;
|
||||
base::PostTaskAndReplyWithResult(
|
||||
task_runner.get(), FROM_HERE,
|
||||
base::BindOnce(&base::ReadFileToString, file_path, data),
|
||||
base::BindOnce(&OnWallpaperDataRead, std::move(callback),
|
||||
base::WrapUnique(data)));
|
||||
DecodeImageFile(std::move(callback), file_path);
|
||||
}
|
||||
|
||||
bool WallpaperControllerImpl::InitializeUserWallpaperInfo(
|
||||
@ -2083,6 +2071,22 @@ void WallpaperControllerImpl::SaveAndSetWallpaperWithCompletion(
|
||||
CustomWallpaperElement(wallpaper_path, image);
|
||||
}
|
||||
|
||||
void WallpaperControllerImpl::OnCustomWallpaperDecoded(
|
||||
const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const base::FilePath& path,
|
||||
WallpaperLayout layout,
|
||||
bool preview_mode,
|
||||
SetCustomWallpaperCallback callback,
|
||||
const gfx::ImageSkia& image) {
|
||||
bool success = !image.isNull();
|
||||
if (success) {
|
||||
SetCustomWallpaper(account_id, wallpaper_files_id, path.BaseName().value(),
|
||||
layout, image, preview_mode);
|
||||
}
|
||||
std::move(callback).Run(success);
|
||||
}
|
||||
|
||||
void WallpaperControllerImpl::OnWallpaperDecoded(const AccountId& account_id,
|
||||
const base::FilePath& path,
|
||||
const WallpaperInfo& info,
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "ash/public/cpp/wallpaper_types.h"
|
||||
#include "ash/shell_observer.h"
|
||||
#include "ash/wallpaper/wallpaper_utils/wallpaper_color_calculator_observer.h"
|
||||
#include "ash/wallpaper/wallpaper_utils/wallpaper_decoder.h"
|
||||
#include "ash/wallpaper/wallpaper_utils/wallpaper_resizer_observer.h"
|
||||
#include "ash/wm/overview/overview_observer.h"
|
||||
#include "base/files/file_path.h"
|
||||
@ -59,8 +60,6 @@ class WallpaperWindowStateManager;
|
||||
using CustomWallpaperElement = std::pair<base::FilePath, gfx::ImageSkia>;
|
||||
using CustomWallpaperMap = std::map<AccountId, CustomWallpaperElement>;
|
||||
|
||||
using LoadedCallback = base::OnceCallback<void(const gfx::ImageSkia& image)>;
|
||||
|
||||
// Controls the desktop background wallpaper:
|
||||
// - Sets a wallpaper image and layout;
|
||||
// - Handles display change (add/remove display, configuration change etc);
|
||||
@ -233,6 +232,12 @@ class ASH_EXPORT WallpaperControllerImpl
|
||||
const base::FilePath& wallpapers,
|
||||
const base::FilePath& custom_wallpapers,
|
||||
const base::FilePath& device_policy_wallpaper) override;
|
||||
void SetCustomWallpaper(const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const base::FilePath& file_path,
|
||||
WallpaperLayout layout,
|
||||
bool preview_mode,
|
||||
SetCustomWallpaperCallback callback) override;
|
||||
void SetCustomWallpaper(const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const std::string& file_name,
|
||||
@ -427,7 +432,7 @@ class ASH_EXPORT WallpaperControllerImpl
|
||||
// Reads image from |file_path| on disk, and calls |OnWallpaperDataRead|
|
||||
// with the result of |ReadFileToString|.
|
||||
void ReadAndDecodeWallpaper(
|
||||
LoadedCallback callback,
|
||||
DecodeImageCallback callback,
|
||||
scoped_refptr<base::SequencedTaskRunner> task_runner,
|
||||
const base::FilePath& file_path);
|
||||
|
||||
@ -493,10 +498,18 @@ class ASH_EXPORT WallpaperControllerImpl
|
||||
const gfx::ImageSkia& image,
|
||||
base::OnceCallback<void(const base::FilePath&)> image_saved_callback);
|
||||
|
||||
void OnCustomWallpaperDecoded(const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const base::FilePath& path,
|
||||
WallpaperLayout layout,
|
||||
bool preview_mode,
|
||||
SetCustomWallpaperCallback callback,
|
||||
const gfx::ImageSkia& image);
|
||||
|
||||
// Used as the callback of wallpaper decoding. (Wallpapers of type ONLINE,
|
||||
// DEFAULT and DEVICE should use their corresponding |*Decoded|, and all other
|
||||
// types should use this.) Shows the wallpaper immediately if |show_wallpaper|
|
||||
// is true. Otherwise, only updates the cache.
|
||||
// DEFAULT, CUSTOM, and DEVICE should use their corresponding |*Decoded|,
|
||||
// and all other types should use this.) Shows the wallpaper immediately if
|
||||
// |show_wallpaper| is true. Otherwise, only updates the cache.
|
||||
void OnWallpaperDecoded(const AccountId& account_id,
|
||||
const base::FilePath& path,
|
||||
const WallpaperInfo& info,
|
||||
|
@ -1,44 +1,71 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Copyright 2021 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 "ash/wallpaper/wallpaper_utils/wallpaper_decoder.h"
|
||||
|
||||
#include "ash/shell.h"
|
||||
#include "ash/shell_delegate.h"
|
||||
#include <string>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/callback.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/task/task_traits.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "ipc/ipc_channel.h"
|
||||
#include "services/data_decoder/public/cpp/decode_image.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
namespace {
|
||||
|
||||
const int64_t kMaxImageSizeInBytes =
|
||||
static_cast<int64_t>(IPC::Channel::kMaximumMessageSize);
|
||||
|
||||
void ConvertToImageSkia(OnWallpaperDecoded callback, const SkBitmap& image) {
|
||||
if (image.isNull()) {
|
||||
std::string ReadFileToString(const base::FilePath& path) {
|
||||
std::string result;
|
||||
if (!base::ReadFileToString(path, &result)) {
|
||||
LOG(WARNING) << "Failed reading file";
|
||||
result.clear();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ToImageSkia(DecodeImageCallback callback, const SkBitmap& bitmap) {
|
||||
if (bitmap.empty()) {
|
||||
LOG(WARNING) << "Failed to decode image";
|
||||
std::move(callback).Run(gfx::ImageSkia());
|
||||
return;
|
||||
}
|
||||
SkBitmap final_image = image;
|
||||
final_image.setImmutable();
|
||||
gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(final_image);
|
||||
image_skia.MakeThreadSafe();
|
||||
|
||||
std::move(callback).Run(image_skia);
|
||||
gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
|
||||
std::move(callback).Run(image);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DecodeWallpaper(const std::string& image_data,
|
||||
const data_decoder::mojom::ImageCodec& image_codec,
|
||||
OnWallpaperDecoded callback) {
|
||||
std::vector<uint8_t> image_bytes(image_data.begin(), image_data.end());
|
||||
void DecodeImageFile(DecodeImageCallback callback,
|
||||
const base::FilePath& file_path) {
|
||||
base::ThreadPool::PostTaskAndReplyWithResult(
|
||||
FROM_HERE,
|
||||
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
|
||||
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
|
||||
base::BindOnce(&ReadFileToString, file_path),
|
||||
base::BindOnce(&DecodeImageData, std::move(callback)));
|
||||
}
|
||||
|
||||
void DecodeImageData(DecodeImageCallback callback, const std::string& data) {
|
||||
if (data.empty()) {
|
||||
std::move(callback).Run(gfx::ImageSkia());
|
||||
return;
|
||||
}
|
||||
data_decoder::DecodeImageIsolated(
|
||||
std::move(image_bytes), image_codec, /*shrink_to_fit=*/true,
|
||||
kMaxImageSizeInBytes, /*desired_image_frame_size=*/gfx::Size(),
|
||||
base::BindOnce(&ConvertToImageSkia, std::move(callback)));
|
||||
std::vector<uint8_t>(data.begin(), data.end()),
|
||||
data_decoder::mojom::ImageCodec::kDefault,
|
||||
/*shrink_to_fit=*/false, kMaxImageSizeInBytes,
|
||||
/*desired_image_frame_size=*/gfx::Size(),
|
||||
base::BindOnce(&ToImageSkia, std::move(callback)));
|
||||
}
|
||||
|
||||
} // namespace ash
|
||||
|
@ -1,13 +1,17 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Copyright 2021 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 ASH_WALLPAPER_WALLPAPER_UTILS_WALLPAPER_DECODER_H_
|
||||
#define ASH_WALLPAPER_WALLPAPER_UTILS_WALLPAPER_DECODER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "services/data_decoder/public/cpp/decode_image.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
} // namespace base
|
||||
|
||||
namespace gfx {
|
||||
class ImageSkia;
|
||||
@ -15,14 +19,12 @@ class ImageSkia;
|
||||
|
||||
namespace ash {
|
||||
|
||||
using OnWallpaperDecoded =
|
||||
base::OnceCallback<void(const gfx::ImageSkia& image)>;
|
||||
using DecodeImageCallback = base::OnceCallback<void(const gfx::ImageSkia&)>;
|
||||
|
||||
// Do an async wallpaper decode; |on_decoded| is run on the calling thread when
|
||||
// the decode has finished.
|
||||
void DecodeWallpaper(const std::string& image_data,
|
||||
const data_decoder::mojom::ImageCodec& image_codec,
|
||||
OnWallpaperDecoded callback);
|
||||
void DecodeImageFile(DecodeImageCallback callback,
|
||||
const base::FilePath& file_path);
|
||||
|
||||
void DecodeImageData(DecodeImageCallback callback, const std::string& data);
|
||||
|
||||
} // namespace ash
|
||||
|
||||
|
26
chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
26
chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
@ -12,6 +12,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ash/public/cpp/wallpaper_controller.h"
|
||||
#include "ash/public/cpp/wallpaper_info.h"
|
||||
#include "ash/public/cpp/wallpaper_types.h"
|
||||
#include "base/bind.h"
|
||||
@ -237,6 +238,31 @@ void ChromePersonalizationAppUiDelegate::SelectWallpaper(
|
||||
/*preview_mode=*/false, std::move(callback));
|
||||
}
|
||||
|
||||
void ChromePersonalizationAppUiDelegate::SelectLocalImage(
|
||||
const base::UnguessableToken& id,
|
||||
SelectLocalImageCallback callback) {
|
||||
const auto& it = local_image_id_map_.find(id);
|
||||
|
||||
if (it == local_image_id_map_.end()) {
|
||||
mojo::ReportBadMessage("Invalid local image id selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const user_manager::User* user =
|
||||
chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
|
||||
DCHECK(user);
|
||||
|
||||
auto* controller = ash::WallpaperController::Get();
|
||||
auto* client = WallpaperControllerClientImpl::Get();
|
||||
|
||||
const auto& account_id = user->GetAccountId();
|
||||
|
||||
controller->SetCustomWallpaper(
|
||||
account_id, client->GetFilesId(account_id), it->second,
|
||||
ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
|
||||
/*preview_mode=*/false, std::move(callback));
|
||||
}
|
||||
|
||||
void ChromePersonalizationAppUiDelegate::OnFetchCollections(
|
||||
FetchCollectionsCallback callback,
|
||||
bool success,
|
||||
|
@ -77,6 +77,9 @@ class ChromePersonalizationAppUiDelegate : public PersonalizationAppUiDelegate {
|
||||
void SelectWallpaper(uint64_t image_asset_id,
|
||||
SelectWallpaperCallback callback) override;
|
||||
|
||||
void SelectLocalImage(const base::UnguessableToken& id,
|
||||
SelectLocalImageCallback callback) override;
|
||||
|
||||
private:
|
||||
mojo::Receiver<chromeos::personalization_app::mojom::WallpaperProvider>
|
||||
wallpaper_receiver_{this};
|
||||
|
@ -38,6 +38,17 @@ void TestWallpaperController::Init(
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void TestWallpaperController::SetCustomWallpaper(
|
||||
const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const base::FilePath& file_path,
|
||||
ash::WallpaperLayout layout,
|
||||
bool preview_mode,
|
||||
SetCustomWallpaperCallback callback) {
|
||||
++set_custom_wallpaper_count_;
|
||||
std::move(callback).Run(true);
|
||||
}
|
||||
|
||||
void TestWallpaperController::SetCustomWallpaper(
|
||||
const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
|
@ -45,6 +45,12 @@ class TestWallpaperController : public ash::WallpaperController {
|
||||
const base::FilePath& wallpapers,
|
||||
const base::FilePath& custom_wallpapers,
|
||||
const base::FilePath& device_policy_wallpaper) override;
|
||||
void SetCustomWallpaper(const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const base::FilePath& file_path,
|
||||
ash::WallpaperLayout layout,
|
||||
bool preview_mode,
|
||||
SetCustomWallpaperCallback callback) override;
|
||||
void SetCustomWallpaper(const AccountId& account_id,
|
||||
const std::string& wallpaper_files_id,
|
||||
const std::string& file_name,
|
||||
|
@ -13,6 +13,7 @@ js_type_check("closure_compile") {
|
||||
"js_module_root=./gen/chrome/test/data/webui/",
|
||||
]
|
||||
deps = [
|
||||
":local_images_element_test",
|
||||
":personalization_app_test_utils",
|
||||
":personalization_app_unified_test",
|
||||
":test_mojo_interface_provider",
|
||||
@ -23,6 +24,18 @@ js_type_check("closure_compile") {
|
||||
]
|
||||
}
|
||||
|
||||
js_library("local_images_element_test") {
|
||||
deps = [
|
||||
":personalization_app_test_utils",
|
||||
":test_mojo_interface_provider",
|
||||
":test_personalization_store",
|
||||
"../..:chai_assert",
|
||||
"../..:test_util.m",
|
||||
"//chromeos/components/personalization_app/resources/trusted:local_images_element",
|
||||
]
|
||||
externs_list = [ "$externs_path/mocha-2.5.js" ]
|
||||
}
|
||||
|
||||
js_library("personalization_app_test_utils") {
|
||||
deps = [
|
||||
":test_mojo_interface_provider",
|
||||
@ -36,6 +49,7 @@ js_library("personalization_app_test_utils") {
|
||||
|
||||
js_library("personalization_app_unified_test") {
|
||||
deps = [
|
||||
":local_images_element_test",
|
||||
":wallpaper_collections_element_test",
|
||||
":wallpaper_images_element_test",
|
||||
":wallpaper_selected_element_test",
|
||||
|
@ -0,0 +1,118 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
import {LocalImages} from 'chrome://personalization/trusted/local_images_element.js';
|
||||
import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
|
||||
import {flushTasks, waitAfterNextRender} from '../../test_util.m.js';
|
||||
import {baseSetup, initElement, teardownElement} from './personalization_app_test_utils.js';
|
||||
import {TestWallpaperProvider} from './test_mojo_interface_provider.js';
|
||||
import {TestPersonalizationStore} from './test_personalization_store.js';
|
||||
|
||||
export function LocalImagesTest() {
|
||||
/** @type {?HTMLElement} */
|
||||
let localImagesElement = null;
|
||||
|
||||
/** @type {?TestWallpaperProvider} */
|
||||
let wallpaperProvider = null;
|
||||
|
||||
/** @type {?TestPersonalizationStore} */
|
||||
let personalizationStore = null;
|
||||
|
||||
setup(() => {
|
||||
const mocks = baseSetup();
|
||||
wallpaperProvider = mocks.wallpaperProvider;
|
||||
personalizationStore = mocks.personalizationStore;
|
||||
});
|
||||
|
||||
teardown(async () => {
|
||||
await teardownElement(localImagesElement);
|
||||
localImagesElement = null;
|
||||
await flushTasks();
|
||||
});
|
||||
|
||||
test(
|
||||
'displays loading spinner while local image list is loading',
|
||||
async () => {
|
||||
personalizationStore.data.loading.local = {images: true, data: {}};
|
||||
|
||||
localImagesElement = initElement(LocalImages.is, {hidden: false});
|
||||
|
||||
const spinner =
|
||||
localImagesElement.shadowRoot.querySelector('paper-spinner-lite');
|
||||
assertTrue(spinner.active);
|
||||
|
||||
personalizationStore.data.loading.local = {images: false, data: {}};
|
||||
personalizationStore.notifyObservers();
|
||||
|
||||
await waitAfterNextRender(localImagesElement);
|
||||
|
||||
assertFalse(spinner.active);
|
||||
});
|
||||
|
||||
test(
|
||||
'displays images for current local images that have successfully loaded',
|
||||
async () => {
|
||||
personalizationStore.data.local = {
|
||||
images: wallpaperProvider.localImages,
|
||||
data: wallpaperProvider.localImageData,
|
||||
};
|
||||
personalizationStore.data.loading.local = {images: false, data: {}};
|
||||
|
||||
localImagesElement = initElement(LocalImages.is, {hidden: false});
|
||||
|
||||
const ironList =
|
||||
localImagesElement.shadowRoot.querySelector('iron-list');
|
||||
assertTrue(!!ironList);
|
||||
|
||||
// Both items are sent. No images are rendered yet because they are not
|
||||
// done loading thumbnails.
|
||||
assertEquals(2, ironList.items.length);
|
||||
assertEquals(0, ironList.shadowRoot.querySelectorAll('img').length);
|
||||
|
||||
// Set loading finished for first thumbnail.
|
||||
personalizationStore.data.loading.local.data = {'100,10': false};
|
||||
personalizationStore.notifyObservers();
|
||||
await waitAfterNextRender(localImagesElement);
|
||||
assertEquals(2, ironList.items.length);
|
||||
let imgTags = localImagesElement.shadowRoot.querySelectorAll('img');
|
||||
assertEquals(1, imgTags.length);
|
||||
assertEquals('data://localimage0data', imgTags[0].src);
|
||||
|
||||
// Set loading failed for second thumbnail.
|
||||
personalizationStore.data.loading.local.data = {
|
||||
'100,10': false,
|
||||
'200,20': false
|
||||
};
|
||||
personalizationStore.data.local.data = {
|
||||
'100,10': 'data://localimage0data',
|
||||
'200,20': null
|
||||
};
|
||||
personalizationStore.notifyObservers();
|
||||
await waitAfterNextRender(localImagesElement);
|
||||
// Still only first thumbnail displayed.
|
||||
imgTags = localImagesElement.shadowRoot.querySelectorAll('img');
|
||||
assertEquals(1, imgTags.length);
|
||||
assertEquals('data://localimage0data', imgTags[0].src);
|
||||
});
|
||||
|
||||
test('displays error on loading failure', async () => {
|
||||
personalizationStore.data.local = {images: null, data: {}};
|
||||
personalizationStore.data.loading.local = {images: false, data: {}};
|
||||
|
||||
localImagesElement = initElement(LocalImages.is, {hidden: false});
|
||||
|
||||
// Spinner is not active because loading is finished.
|
||||
const spinner =
|
||||
localImagesElement.shadowRoot.querySelector('paper-spinner-lite');
|
||||
assertFalse(spinner.active);
|
||||
|
||||
// Error should be visible.
|
||||
const error = localImagesElement.shadowRoot.querySelector('#error');
|
||||
assertFalse(error.hidden);
|
||||
|
||||
// No iron-list displayed.
|
||||
const ironList = localImagesElement.shadowRoot.querySelector('iron-list');
|
||||
assertFalse(!!ironList);
|
||||
});
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import {LocalImagesTest} from './local_images_element_test.js';
|
||||
import {WallpaperBreadcrumbTest} from './wallpaper_breadcrumb_element_test.js';
|
||||
import {WallpaperCollectionsTest} from './wallpaper_collections_element_test.js';
|
||||
import {WallpaperImagesTest} from './wallpaper_images_element_test.js';
|
||||
@ -12,6 +13,7 @@ import {WallpaperSelectedTest} from './wallpaper_selected_element_test.js';
|
||||
window.console.warn = () => {};
|
||||
|
||||
const testCases = [
|
||||
LocalImagesTest,
|
||||
WallpaperBreadcrumbTest,
|
||||
WallpaperCollectionsTest,
|
||||
WallpaperImagesTest,
|
||||
|
@ -71,8 +71,8 @@ export class TestWallpaperProvider extends TestBrowserProxy {
|
||||
|
||||
/** @type {!Object<string, string>} */
|
||||
this.localImageData = {
|
||||
'100,10': 'localimage0data',
|
||||
'200,20': 'localimage1data',
|
||||
'100,10': 'data://localimage0data',
|
||||
'200,20': 'data://localimage1data',
|
||||
};
|
||||
|
||||
/**
|
||||
@ -83,6 +83,8 @@ export class TestWallpaperProvider extends TestBrowserProxy {
|
||||
|
||||
/** @public */
|
||||
this.selectWallpaperResponse = true;
|
||||
|
||||
this.selectLocalImageResponse = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,6 +142,12 @@ export class TestWallpaperProvider extends TestBrowserProxy {
|
||||
return Promise.resolve({success: this.selectWallpaperResponse});
|
||||
}
|
||||
|
||||
/** @override */
|
||||
selectLocalImage(id) {
|
||||
this.methodCalled('selectLocalImage', id);
|
||||
return Promise.resolve({success: this.selectLocalImageResponse});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<!chromeos.personalizationApp.mojom.WallpaperCollection>}
|
||||
* collections
|
||||
|
@ -32,7 +32,8 @@ struct WallpaperImage {
|
||||
};
|
||||
|
||||
struct LocalImage {
|
||||
// Id to be used for fetching the image thumbnail data.
|
||||
// Unique ID for this image for fetching thumbnail data and setting as user
|
||||
// background.
|
||||
mojo_base.mojom.UnguessableToken id;
|
||||
|
||||
// Name of this local asset to be displayed to the user.
|
||||
@ -67,6 +68,9 @@ interface WallpaperProvider {
|
||||
// on failure.
|
||||
GetCurrentWallpaper() => (WallpaperImage? image);
|
||||
|
||||
// Sets the given wallpaper as the user's background.
|
||||
// Sets the given backdrop wallpaper as the user's background.
|
||||
SelectWallpaper(uint64 image_asset_id) => (bool success);
|
||||
|
||||
// Sets the given local wallpaper as the user's background.
|
||||
SelectLocalImage(mojo_base.mojom.UnguessableToken id) => (bool success);
|
||||
};
|
||||
|
@ -44,3 +44,22 @@ export function promisifyOnload(element, id, afterNextRender) {
|
||||
export function unguessableTokenToString({high, low}) {
|
||||
return `${high},${low}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!string} str
|
||||
* @return {!mojoBase.mojom.UnguessableToken}
|
||||
*/
|
||||
export function stringToUnguessableToken(str) {
|
||||
const [high, low] = str.split(',').map(BigInt);
|
||||
return {high, low};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!mojoBase.mojom.UnguessableToken} a
|
||||
* @param {!mojoBase.mojom.UnguessableToken} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function unguessableTokensEqual(a, b) {
|
||||
return a.hasOwnProperty('high') && a.high === b.high &&
|
||||
a.hasOwnProperty('low') && a.low === b.low;
|
||||
}
|
||||
|
@ -1,14 +1,20 @@
|
||||
<style include="trusted-style common-style"></style>
|
||||
<paper-spinner-lite active="[[imagesLoading_]]">
|
||||
</paper-spinner-lite>
|
||||
<iron-list items="[[getImages_(hidden, images_)]]" grid>
|
||||
<template>
|
||||
<div class="photo-container">
|
||||
<template is="dom-if"
|
||||
<!-- TODO(b/181697575) handle error cases and update error string to UI spec -->
|
||||
<p hidden$="[[!hasError_]]" id="error">error</p>
|
||||
<template is="dom-if" if="[[showImages_]]">
|
||||
<iron-list items="[[getImages_(hidden, images_)]]" grid>
|
||||
<template>
|
||||
<div class="photo-container">
|
||||
<template is="dom-if"
|
||||
if="[[shouldShowImage_(item, imageData_, imageDataLoading_)]]">
|
||||
<img src="[[getImageData_(item, imageData_)]]">
|
||||
</template>
|
||||
<p class="image-name">[[item.name]]</p>
|
||||
</div>
|
||||
</template>
|
||||
</iron-list>
|
||||
<div on-click="onClickImage_" data-id$="[[getImageKey_(item)]]">
|
||||
<img src="[[getImageData_(item, imageData_)]]">
|
||||
</div>
|
||||
</template>
|
||||
<p class="image-name">[[item.name]]</p>
|
||||
</div>
|
||||
</template>
|
||||
</iron-list>
|
||||
</template>
|
||||
|
@ -13,8 +13,11 @@ import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
|
||||
import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
|
||||
import './styles.js';
|
||||
import '../common/styles.js';
|
||||
import {assert} from '/assert.m.js';
|
||||
import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
import {unguessableTokenToString} from '../common/utils.js';
|
||||
import {isNonEmptyArray, stringToUnguessableToken, unguessableTokensEqual, unguessableTokenToString} from '../common/utils.js';
|
||||
import {getWallpaperProvider} from './mojo_interface_provider.js';
|
||||
import {selectWallpaper} from './personalization_controller.js';
|
||||
import {WithPersonalizationStore} from './personalization_store.js';
|
||||
|
||||
/** @polymer */
|
||||
@ -65,6 +68,18 @@ export class LocalImages extends WithPersonalizationStore {
|
||||
imageDataLoading_: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
/** @private */
|
||||
hasError_: {
|
||||
type: Boolean,
|
||||
computed: 'computeHasError_(imagesLoading_, images_)',
|
||||
},
|
||||
|
||||
/** @private */
|
||||
showImages_: {
|
||||
type: Boolean,
|
||||
computed: 'computeShowImages_(imagesLoading_, images_)'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -78,6 +93,26 @@ export class LocalImages extends WithPersonalizationStore {
|
||||
this.updateFromStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {boolean} imagesLoading
|
||||
* @param {?Array<!chromeos.personalizationApp.mojom.LocalImage>} images
|
||||
* @return {boolean}
|
||||
*/
|
||||
computeHasError_(imagesLoading, images) {
|
||||
return !imagesLoading && !isNonEmptyArray(images);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {boolean} imagesLoading
|
||||
* @param {?Array<!chromeos.personalizationApp.mojom.LocalImage>} images
|
||||
* @return {boolean}
|
||||
*/
|
||||
computeShowImages_(imagesLoading, images) {
|
||||
return !imagesLoading && isNonEmptyArray(images);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces iron-list to re-evaluate when hidden changes.
|
||||
* @private
|
||||
@ -114,6 +149,29 @@ export class LocalImages extends WithPersonalizationStore {
|
||||
const key = unguessableTokenToString(image.id);
|
||||
return imageData[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {!chromeos.personalizationApp.mojom.LocalImage} image
|
||||
* @return {string}
|
||||
*/
|
||||
getImageKey_(image) {
|
||||
return unguessableTokenToString(image.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {!Event} event
|
||||
*/
|
||||
onClickImage_(event) {
|
||||
const id = stringToUnguessableToken(event.currentTarget.dataset.id);
|
||||
const image =
|
||||
this.images_.find(image => unguessableTokensEqual(id, image.id));
|
||||
assert(!!image, 'Image with that id not found');
|
||||
selectWallpaper(
|
||||
/** @type {!chromeos.personalizationApp.mojom.LocalImage} */ (image),
|
||||
getWallpaperProvider(), this.getStore());
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(LocalImages.is, LocalImages);
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import {Action} from 'chrome://resources/js/cr/ui/store.m.js';
|
||||
import {unguessableTokenToString} from '../common/utils.js';
|
||||
import {DisplayableImage} from './personalization_reducers.js';
|
||||
|
||||
/**
|
||||
* @fileoverview Defines the actions to change state.
|
||||
@ -49,7 +50,7 @@ export function beginLoadLocalImageDataAction(image) {
|
||||
|
||||
/**
|
||||
* Notify that a user has clicked on an image to set as wallpaper.
|
||||
* @param {!chromeos.personalizationApp.mojom.WallpaperImage} image
|
||||
* @param {!DisplayableImage} image
|
||||
* @return {!Action}
|
||||
*/
|
||||
export function beginSelectImageAction(image) {
|
||||
@ -114,7 +115,7 @@ export function setLocalImagesAction(images) {
|
||||
* Returns an action to set the current image as currently selected across the
|
||||
* app. Can be called with null to represent no image currently selected or that
|
||||
* an error occurred.
|
||||
* @param {?chromeos.personalizationApp.mojom.WallpaperImage} image
|
||||
* @param {?DisplayableImage} image
|
||||
* @return {!Action}
|
||||
*/
|
||||
export function setSelectedImageAction(image) {
|
||||
|
@ -102,7 +102,8 @@ export async function getCurrentWallpaper(provider, store) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!chromeos.personalizationApp.mojom.WallpaperImage} image
|
||||
* @param {!chromeos.personalizationApp.mojom.WallpaperImage |
|
||||
* !chromeos.personalizationApp.mojom.LocalImage} image
|
||||
* @param {!chromeos.personalizationApp.mojom.WallpaperProviderInterface}
|
||||
* provider
|
||||
* @param {!PersonalizationStore} store
|
||||
@ -110,7 +111,16 @@ export async function getCurrentWallpaper(provider, store) {
|
||||
export async function selectWallpaper(image, provider, store) {
|
||||
const oldImage = store.data.selected;
|
||||
store.dispatch(beginSelectImageAction(image));
|
||||
const {success} = await provider.selectWallpaper(image.assetId);
|
||||
const {success} = await (() => {
|
||||
if (image.assetId) {
|
||||
return provider.selectWallpaper(image.assetId);
|
||||
} else if (image.id) {
|
||||
return provider.selectLocalImage(image.id);
|
||||
} else {
|
||||
console.warn('Image must be a LocalImage or a WallpaperImage');
|
||||
return {success: false};
|
||||
}
|
||||
})();
|
||||
if (!success) {
|
||||
console.warn('Error setting wallpaper');
|
||||
}
|
||||
|
@ -11,6 +11,12 @@
|
||||
import {Action} from 'chrome://resources/js/cr/ui/store.m.js';
|
||||
import {ActionName} from './personalization_actions.js';
|
||||
|
||||
/**
|
||||
* @typedef {chromeos.personalizationApp.mojom.LocalImage|
|
||||
* chromeos.personalizationApp.mojom.WallpaperImage}
|
||||
*/
|
||||
export let DisplayableImage;
|
||||
|
||||
/**
|
||||
* Stores collections and images from backdrop server.
|
||||
* |images| is a mapping of collection id to the list of images.
|
||||
@ -61,7 +67,7 @@ export let LocalState;
|
||||
* backdrop: !BackdropState,
|
||||
* loading: !LoadingState,
|
||||
* local: !LocalState,
|
||||
* selected: ?chromeos.personalizationApp.mojom.WallpaperImage,
|
||||
* selected: ?DisplayableImage,
|
||||
* }}
|
||||
*/
|
||||
export let PersonalizationState;
|
||||
@ -217,9 +223,9 @@ function localReducer(state, action) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?chromeos.personalizationApp.mojom.WallpaperImage} state
|
||||
* @param {?DisplayableImage} state
|
||||
* @param {!Action} action
|
||||
* @return {?chromeos.personalizationApp.mojom.WallpaperImage}
|
||||
* @return {?DisplayableImage}
|
||||
*/
|
||||
function selectedReducer(state, action) {
|
||||
switch (action.name) {
|
||||
|
@ -73,3 +73,9 @@ void FakePersonalizationAppUiDelegate::SelectWallpaper(
|
||||
SelectWallpaperCallback callback) {
|
||||
std::move(callback).Run(/*success=*/true);
|
||||
}
|
||||
|
||||
void FakePersonalizationAppUiDelegate::SelectLocalImage(
|
||||
const base::UnguessableToken& token,
|
||||
SelectLocalImageCallback callback) {
|
||||
std::move(callback).Run(/*success=*/true);
|
||||
}
|
||||
|
@ -50,6 +50,9 @@ class FakePersonalizationAppUiDelegate : public PersonalizationAppUiDelegate {
|
||||
void SelectWallpaper(uint64_t image_asset_id,
|
||||
SelectWallpaperCallback callback) override;
|
||||
|
||||
void SelectLocalImage(const base::UnguessableToken& token,
|
||||
SelectLocalImageCallback callback) override;
|
||||
|
||||
private:
|
||||
mojo::Receiver<chromeos::personalization_app::mojom::WallpaperProvider>
|
||||
wallpaper_receiver_{this};
|
||||
|
Reference in New Issue
Block a user