1 // Copyright 2017 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/vr/assets_loader.h"
6 
7 #include "base/bind.h"
8 #include "base/files/file_util.h"
9 #include "base/memory/singleton.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/task/task_traits.h"
12 #include "base/task/thread_pool.h"
13 #include "base/threading/thread_task_runner_handle.h"
14 #include "base/values.h"
15 #include "chrome/browser/vr/model/assets.h"
16 #include "chrome/browser/vr/vr_buildflags.h"
17 #include "content/public/browser/browser_task_traits.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "media/audio/wav_audio_handler.h"
20 #include "third_party/skia/include/core/SkBitmap.h"
21 #include "ui/gfx/codec/jpeg_codec.h"
22 #include "ui/gfx/codec/png_codec.h"
23 
24 namespace vr {
25 
26 namespace {
27 
28 constexpr char kMinVersionWithGradients[] = "1.1";
29 constexpr char kMinVersionWithSounds[] = "2.0";
30 constexpr char kMinVersionWithInactiveButtonClickSound[] = "2.2";
31 
32 static const base::FilePath::CharType kBackgroundBaseFilename[] =
33     FILE_PATH_LITERAL("background");
34 static const base::FilePath::CharType kNormalGradientBaseFilename[] =
35     FILE_PATH_LITERAL("normal_gradient");
36 static const base::FilePath::CharType kIncognitoGradientBaseFilename[] =
37     FILE_PATH_LITERAL("incognito_gradient");
38 static const base::FilePath::CharType kFullscreenGradientBaseFilename[] =
39     FILE_PATH_LITERAL("fullscreen_gradient");
40 static const base::FilePath::CharType kPngExtension[] =
41     FILE_PATH_LITERAL("png");
42 static const base::FilePath::CharType kJpegExtension[] =
43     FILE_PATH_LITERAL("jpeg");
44 
45 static const base::FilePath::CharType kButtonHoverSoundFilename[] =
46     FILE_PATH_LITERAL("button_hover.wav");
47 static const base::FilePath::CharType kButtonClickSoundFilename[] =
48     FILE_PATH_LITERAL("button_click.wav");
49 static const base::FilePath::CharType kBackButtonClickSoundFilename[] =
50     FILE_PATH_LITERAL("back_button_click.wav");
51 static const base::FilePath::CharType kInactiveButtonClickSoundFilename[] =
52     FILE_PATH_LITERAL("inactive_button_click.wav");
53 
54 }  // namespace
55 
56 struct AssetsLoaderSingletonTrait
57     : public base::DefaultSingletonTraits<AssetsLoader> {
Newvr::AssetsLoaderSingletonTrait58   static AssetsLoader* New() { return new AssetsLoader(); }
Deletevr::AssetsLoaderSingletonTrait59   static void Delete(AssetsLoader* assets) { delete assets; }
60 };
61 
62 // static
GetInstance()63 AssetsLoader* AssetsLoader::GetInstance() {
64   return base::Singleton<AssetsLoader, AssetsLoaderSingletonTrait>::get();
65 }
66 
67 // static
MinVersionWithGradients()68 base::Version AssetsLoader::MinVersionWithGradients() {
69   return base::Version(kMinVersionWithGradients);
70 }
71 
72 // static
AssetsSupported()73 bool AssetsLoader::AssetsSupported() {
74 #if BUILDFLAG(USE_VR_ASSETS_COMPONENT)
75   return true;
76 #else   // BUILDFLAG(USE_VR_ASSETS_COMPONENT)
77   return false;
78 #endif  // BUILDFLAG(USE_VR_ASSETS_COMPONENT)
79 }
80 
OnComponentReady(const base::Version & version,const base::FilePath & install_dir,std::unique_ptr<base::DictionaryValue> manifest)81 void AssetsLoader::OnComponentReady(
82     const base::Version& version,
83     const base::FilePath& install_dir,
84     std::unique_ptr<base::DictionaryValue> manifest) {
85   main_thread_task_runner_->PostTask(
86       FROM_HERE,
87       base::BindOnce(&AssetsLoader::OnComponentReadyInternal,
88                      weak_ptr_factory_.GetWeakPtr(), version, install_dir));
89 }
90 
Load(OnAssetsLoadedCallback on_loaded)91 void AssetsLoader::Load(OnAssetsLoadedCallback on_loaded) {
92   main_thread_task_runner_->PostTask(
93       FROM_HERE, base::BindOnce(&AssetsLoader::LoadInternal,
94                                 weak_ptr_factory_.GetWeakPtr(),
95                                 base::ThreadTaskRunnerHandle::Get(),
96                                 std::move(on_loaded)));
97 }
98 
ComponentReady()99 bool AssetsLoader::ComponentReady() {
100   DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
101   return component_ready_;
102 }
103 
SetOnComponentReadyCallback(const base::RepeatingCallback<void ()> & on_component_ready)104 void AssetsLoader::SetOnComponentReadyCallback(
105     const base::RepeatingCallback<void()>& on_component_ready) {
106   DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
107   on_component_ready_callback_ = on_component_ready;
108 }
109 
LoadImage(const base::FilePath & component_install_dir,const base::FilePath::CharType * base_file,std::unique_ptr<SkBitmap> * out_image)110 AssetsLoadStatus LoadImage(const base::FilePath& component_install_dir,
111                            const base::FilePath::CharType* base_file,
112                            std::unique_ptr<SkBitmap>* out_image) {
113   bool is_png = false;
114   std::string encoded_file_content;
115 
116   base::FilePath file_path = component_install_dir.Append(base_file);
117 
118   if (base::PathExists(file_path.AddExtension(kPngExtension))) {
119     file_path = file_path.AddExtension(kPngExtension);
120     is_png = true;
121   } else if (base::PathExists(file_path.AddExtension(kJpegExtension))) {
122     file_path = file_path.AddExtension(kJpegExtension);
123   } else {
124     return AssetsLoadStatus::kNotFound;
125   }
126 
127   if (!base::ReadFileToString(file_path, &encoded_file_content)) {
128     return AssetsLoadStatus::kParseFailure;
129   }
130 
131   if (is_png) {
132     (*out_image) = std::make_unique<SkBitmap>();
133     if (!gfx::PNGCodec::Decode(
134             reinterpret_cast<const unsigned char*>(encoded_file_content.data()),
135             encoded_file_content.size(), out_image->get())) {
136       out_image->reset();
137     }
138   } else {
139     (*out_image) = gfx::JPEGCodec::Decode(
140         reinterpret_cast<const unsigned char*>(encoded_file_content.data()),
141         encoded_file_content.size());
142   }
143 
144   if (!out_image->get()) {
145     return AssetsLoadStatus::kInvalidContent;
146   }
147 
148   return AssetsLoadStatus::kSuccess;
149 }
150 
LoadSound(const base::FilePath & component_install_dir,const base::FilePath::CharType * file_name,std::unique_ptr<std::string> * out_buffer)151 AssetsLoadStatus LoadSound(const base::FilePath& component_install_dir,
152                            const base::FilePath::CharType* file_name,
153                            std::unique_ptr<std::string>* out_buffer) {
154   base::FilePath file_path = component_install_dir.Append(file_name);
155   if (!base::PathExists(file_path)) {
156     return AssetsLoadStatus::kNotFound;
157   }
158 
159   auto buffer = std::make_unique<std::string>();
160   if (!base::ReadFileToString(file_path, buffer.get())) {
161     return AssetsLoadStatus::kParseFailure;
162   }
163 
164   if (!media::WavAudioHandler::Create(*buffer)) {
165     return AssetsLoadStatus::kInvalidContent;
166   }
167 
168   *out_buffer = std::move(buffer);
169   return AssetsLoadStatus::kSuccess;
170 }
171 
172 // static
LoadAssetsTask(scoped_refptr<base::SingleThreadTaskRunner> task_runner,const base::Version & component_version,const base::FilePath & component_install_dir,OnAssetsLoadedCallback on_loaded)173 void AssetsLoader::LoadAssetsTask(
174     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
175     const base::Version& component_version,
176     const base::FilePath& component_install_dir,
177     OnAssetsLoadedCallback on_loaded) {
178   auto assets = std::make_unique<Assets>();
179   AssetsLoadStatus status = AssetsLoadStatus::kSuccess;
180 
181   status = LoadImage(component_install_dir, kBackgroundBaseFilename,
182                      &assets->background);
183 
184   if (component_version >= AssetsLoader::MinVersionWithGradients()) {
185     if (status == AssetsLoadStatus::kSuccess) {
186       status = LoadImage(component_install_dir, kNormalGradientBaseFilename,
187                          &assets->normal_gradient);
188     }
189     if (status == AssetsLoadStatus::kSuccess) {
190       status = LoadImage(component_install_dir, kIncognitoGradientBaseFilename,
191                          &assets->incognito_gradient);
192     }
193     if (status == AssetsLoadStatus::kSuccess) {
194       status = LoadImage(component_install_dir, kFullscreenGradientBaseFilename,
195                          &assets->fullscreen_gradient);
196     }
197   }
198 
199   std::vector<std::tuple<const char*, const base::FilePath::CharType*,
200                          std::unique_ptr<std::string>*>>
201       sounds = {{kMinVersionWithSounds, kButtonHoverSoundFilename,
202                  &assets->button_hover_sound},
203                 {kMinVersionWithSounds, kButtonClickSoundFilename,
204                  &assets->button_click_sound},
205                 {kMinVersionWithSounds, kBackButtonClickSoundFilename,
206                  &assets->back_button_click_sound},
207                 {kMinVersionWithInactiveButtonClickSound,
208                  kInactiveButtonClickSoundFilename,
209                  &assets->inactive_button_click_sound}};
210 
211   auto sounds_iter = sounds.begin();
212   while (status == AssetsLoadStatus::kSuccess && sounds_iter != sounds.end()) {
213     const char* min_version;
214     const base::FilePath::CharType* file_name;
215     std::unique_ptr<std::string>* data;
216     std::tie(min_version, file_name, data) = *sounds_iter;
217     if (component_version >= base::Version(min_version)) {
218       status = LoadSound(component_install_dir, file_name, data);
219     }
220     sounds_iter++;
221   }
222 
223   if (status != AssetsLoadStatus::kSuccess) {
224     assets.reset();
225   }
226 
227   task_runner->PostTask(
228       FROM_HERE, base::BindOnce(std::move(on_loaded), status, std::move(assets),
229                                 component_version));
230 }
231 
AssetsLoader()232 AssetsLoader::AssetsLoader()
233     : main_thread_task_runner_(content::GetUIThreadTaskRunner({})) {
234   DCHECK(main_thread_task_runner_.get());
235 }
236 
237 AssetsLoader::~AssetsLoader() = default;
238 
OnComponentReadyInternal(const base::Version & version,const base::FilePath & install_dir)239 void AssetsLoader::OnComponentReadyInternal(const base::Version& version,
240                                             const base::FilePath& install_dir) {
241   DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
242   component_version_ = version;
243   component_install_dir_ = install_dir;
244   component_ready_ = true;
245   if (on_component_ready_callback_) {
246     on_component_ready_callback_.Run();
247   }
248 }
249 
LoadInternal(scoped_refptr<base::SingleThreadTaskRunner> task_runner,OnAssetsLoadedCallback on_loaded)250 void AssetsLoader::LoadInternal(
251     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
252     OnAssetsLoadedCallback on_loaded) {
253   DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
254   DCHECK(component_ready_);
255   base::ThreadPool::PostTask(
256       FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
257       base::BindOnce(&AssetsLoader::LoadAssetsTask, task_runner,
258                      component_version_, component_install_dir_,
259                      std::move(on_loaded)));
260 }
261 
262 }  // namespace vr
263